diff options
author | Laszlo Agocs <laszlo.agocs@qt.io> | 2019-03-21 20:42:38 +0100 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@qt.io> | 2019-03-21 20:42:47 +0100 |
commit | 6767114285db9d0e16dc278d08f231e8561546b4 (patch) | |
tree | 0945902a2242fd7ec0a1f7fd3e6acbb769e723bd /src/qml/compiler | |
parent | ee076afedccbe1d37306a7972051f84eb036d655 (diff) | |
parent | c32b109e9dea44c6775c2dbf8f164870c1dc8971 (diff) |
Merge remote-tracking branch 'origin/dev' into wip/scenegraphng
Change-Id: Ib5662d80d5b2f58cf4634c54c054545ba9df807e
Diffstat (limited to 'src/qml/compiler')
41 files changed, 10980 insertions, 16876 deletions
diff --git a/src/qml/compiler/compiler.pri b/src/qml/compiler/compiler.pri index 60f548a2b0..da3c173545 100644 --- a/src/qml/compiler/compiler.pri +++ b/src/qml/compiler/compiler.pri @@ -2,26 +2,28 @@ INCLUDEPATH += $$PWD INCLUDEPATH += $$OUT_PWD HEADERS += \ + $$PWD/qv4bytecodegenerator_p.h \ $$PWD/qv4compileddata_p.h \ $$PWD/qv4compiler_p.h \ + $$PWD/qv4compilercontext_p.h \ + $$PWD/qv4compilercontrolflow_p.h \ + $$PWD/qv4compilerscanfunctions_p.h \ $$PWD/qv4codegen_p.h \ - $$PWD/qv4isel_p.h \ - $$PWD/qv4jsir_p.h \ - $$PWD/qv4isel_util_p.h \ - $$PWD/qv4ssa_p.h \ $$PWD/qqmlirbuilder_p.h \ $$PWD/qqmltypecompiler_p.h \ - $$PWD/qv4jssimplifier_p.h + $$PWD/qv4instr_moth_p.h \ + $$PWD/qv4bytecodehandler_p.h SOURCES += \ + $$PWD/qv4bytecodegenerator.cpp \ $$PWD/qv4compileddata.cpp \ $$PWD/qv4compiler.cpp \ + $$PWD/qv4compilercontext.cpp \ + $$PWD/qv4compilerscanfunctions.cpp \ $$PWD/qv4codegen.cpp \ - $$PWD/qv4isel_p.cpp \ - $$PWD/qv4jsir.cpp \ - $$PWD/qv4ssa.cpp \ $$PWD/qqmlirbuilder.cpp \ - $$PWD/qv4jssimplifier.cpp + $$PWD/qv4instr_moth.cpp \ + $$PWD/qv4bytecodehandler.cpp !qmldevtools_build { @@ -40,15 +42,8 @@ SOURCES += \ unix: SOURCES += $$PWD/qv4compilationunitmapper_unix.cpp else: SOURCES += $$PWD/qv4compilationunitmapper_win.cpp - -qtConfig(private_tests):qtConfig(dlopen): QMAKE_USE_PRIVATE += libdl } -qmldevtools_build|qtConfig(qml-interpreter) { - HEADERS += \ - $$PWD/qv4instr_moth_p.h \ - $$PWD/qv4isel_moth_p.h - SOURCES += \ - $$PWD/qv4instr_moth.cpp \ - $$PWD/qv4isel_moth.cpp +gcc { + equals(QT_GCC_MAJOR_VERSION, 5): QMAKE_CXXFLAGS += -fno-strict-aliasing } diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index f766f0e4c8..ea5efcfc66 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -43,8 +43,10 @@ #include <private/qv4compileddata_p.h> #include <private/qqmljsparser_p.h> #include <private/qqmljslexer_p.h> +#include <private/qv4compilerscanfunctions_p.h> #include <QCoreApplication> #include <QCryptographicHash> +#include <cmath> #ifndef V4_BOOTSTRAP #include <private/qqmlglobal_p.h> @@ -60,7 +62,7 @@ QT_USE_NAMESPACE static const quint32 emptyStringIndex = 0; -#ifndef V4_BOOTSTRAP +#if 0 //ndef V4_BOOTSTRAP DEFINE_BOOL_CONFIG_OPTION(lookupHints, QML_LOOKUP_HINTS); #endif // V4_BOOTSTRAP @@ -86,30 +88,32 @@ void Object::init(QQmlJS::MemoryPool *pool, int typeNameIndex, int idIndex, cons flags = QV4::CompiledData::Object::NoFlag; properties = pool->New<PoolList<Property> >(); aliases = pool->New<PoolList<Alias> >(); + qmlEnums = pool->New<PoolList<Enum>>(); qmlSignals = pool->New<PoolList<Signal> >(); bindings = pool->New<PoolList<Binding> >(); functions = pool->New<PoolList<Function> >(); functionsAndExpressions = pool->New<PoolList<CompiledFunctionOrExpression> >(); - declarationsOverride = 0; + declarationsOverride = nullptr; } -QString Object::sanityCheckFunctionNames(const QSet<QString> &illegalNames, QQmlJS::AST::SourceLocation *errorLocation) +QString IRBuilder::sanityCheckFunctionNames(Object *obj, const QSet<QString> &illegalNames, QQmlJS::AST::SourceLocation *errorLocation) { QSet<int> functionNames; - for (Function *f = functions->first; f; f = f->next) { - QQmlJS::AST::FunctionDeclaration *function = f->functionDeclaration; - Q_ASSERT(function); - *errorLocation = function->identifierToken; + for (auto functionit = obj->functionsBegin(); functionit != obj->functionsEnd(); ++functionit) { + Function *f = functionit.ptr; + errorLocation->startLine = f->location.line; + errorLocation->startColumn = f->location.column; if (functionNames.contains(f->nameIndex)) return tr("Duplicate method name"); functionNames.insert(f->nameIndex); - for (QmlIR::Signal *s = qmlSignals->first; s; s = s->next) { + for (auto signalit = obj->signalsBegin(); signalit != obj->signalsEnd(); ++signalit) { + QmlIR::Signal *s = signalit.ptr; if (s->nameIndex == f->nameIndex) return tr("Duplicate method name"); } - const QString name = function->name.toString(); + const QString name = stringAt(f->nameIndex); if (name.at(0).isUpper()) return tr("Method names cannot begin with an upper case letter"); if (illegalNames.contains(name)) @@ -118,6 +122,21 @@ QString Object::sanityCheckFunctionNames(const QSet<QString> &illegalNames, QQml return QString(); // no error } +QString Object::appendEnum(Enum *enumeration) +{ + Object *target = declarationsOverride; + if (!target) + target = this; + + for (Enum *e = qmlEnums->first; e; e = e->next) { + if (e->nameIndex == enumeration->nameIndex) + return tr("Duplicate scoped enum name"); + } + + target->qmlEnums->append(enumeration); + return QString(); // no error +} + QString Object::appendSignal(Signal *signal) { Object *target = declarationsOverride; @@ -215,7 +234,7 @@ Binding *Object::findBinding(quint32 nameIndex) const for (Binding *b = bindings->first; b; b = b->next) if (b->propertyNameIndex == nameIndex) return b; - return 0; + return nullptr; } void Object::insertSorted(Binding *b) @@ -257,7 +276,7 @@ void Document::removeScriptPragmas(QString &script) const QLatin1String pragma("pragma"); const QLatin1String library("library"); - QQmlJS::Lexer l(0); + QQmlJS::Lexer l(nullptr); l.setCode(script, 0); int token = l.lex(); @@ -299,22 +318,21 @@ void Document::removeScriptPragmas(QString &script) Document::Document(bool debugMode) : jsModule(debugMode) - , program(0) - , indexOfRootObject(0) + , program(nullptr) , jsGenerator(&jsModule) { } -ScriptDirectivesCollector::ScriptDirectivesCollector(QQmlJS::Engine *engine, QV4::Compiler::JSUnitGenerator *unitGenerator) - : engine(engine) - , jsGenerator(unitGenerator) - , hasPragmaLibrary(false) +ScriptDirectivesCollector::ScriptDirectivesCollector(Document *doc) + : document(doc) + , engine(&doc->jsParserEngine) + , jsGenerator(&doc->jsGenerator) { } void ScriptDirectivesCollector::pragmaLibrary() { - hasPragmaLibrary = true; + document->jsModule.unitFlags |= QV4::CompiledData::Unit::IsSharedLibrary; } void ScriptDirectivesCollector::importFile(const QString &jsfile, const QString &module, int lineNumber, int column) @@ -325,7 +343,7 @@ void ScriptDirectivesCollector::importFile(const QString &jsfile, const QString import->qualifierIndex = jsGenerator->registerString(module); import->location.line = lineNumber; import->location.column = column; - imports << import; + document->imports << import; } void ScriptDirectivesCollector::importModule(const QString &uri, const QString &version, const QString &module, int lineNumber, int column) @@ -341,21 +359,21 @@ void ScriptDirectivesCollector::importModule(const QString &uri, const QString & import->qualifierIndex = jsGenerator->registerString(module); import->location.line = lineNumber; import->location.column = column; - imports << import; + document->imports << import; } IRBuilder::IRBuilder(const QSet<QString> &illegalNames) : illegalNames(illegalNames) - , _object(0) - , _propertyDeclaration(0) - , pool(0) - , jsGenerator(0) + , _object(nullptr) + , _propertyDeclaration(nullptr) + , pool(nullptr) + , jsGenerator(nullptr) { } bool IRBuilder::generateFromQml(const QString &code, const QString &url, Document *output) { - QQmlJS::AST::UiProgram *program = 0; + QQmlJS::AST::UiProgram *program = nullptr; { QQmlJS::Lexer lexer(&output->jsParserEngine); lexer.setCode(code, /*line = */ 1); @@ -404,7 +422,10 @@ bool IRBuilder::generateFromQml(const QString &code, const QString &url, Documen QQmlJS::AST::UiObjectDefinition *rootObject = QQmlJS::AST::cast<QQmlJS::AST::UiObjectDefinition*>(program->members->member); Q_ASSERT(rootObject); - defineQMLObject(&output->indexOfRootObject, rootObject); + int rootObjectIndex = -1; + if (defineQMLObject(&rootObjectIndex, rootObject)) { + Q_ASSERT(rootObjectIndex == 0); + } qSwap(_imports, output->imports); qSwap(_pragmas, output->pragmas); @@ -459,7 +480,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiObjectDefinition *node) appendBinding(nameLocation, nameLocation, emptyStringIndex, idx); } else { int idx = 0; - if (!defineQMLObject(&idx, /*qualfied type name id*/0, node->qualifiedTypeNameId->firstSourceLocation(), node->initializer, /*declarations should go here*/_object)) + if (!defineQMLObject(&idx, /*qualfied type name id*/nullptr, node->qualifiedTypeNameId->firstSourceLocation(), node->initializer, /*declarations should go here*/_object)) return false; appendBinding(node->qualifiedTypeNameId, idx); } @@ -477,14 +498,14 @@ bool IRBuilder::visit(QQmlJS::AST::UiObjectBinding *node) bool IRBuilder::visit(QQmlJS::AST::UiScriptBinding *node) { - appendBinding(node->qualifiedId, node->statement); + appendBinding(node->qualifiedId, node->statement, node); return false; } bool IRBuilder::visit(QQmlJS::AST::UiArrayBinding *node) { const QQmlJS::AST::SourceLocation qualifiedNameLocation = node->qualifiedId->identifierToken; - Object *object = 0; + Object *object = nullptr; QQmlJS::AST::UiQualifiedId *name = node->qualifiedId; if (!resolveQualifiedId(&name, &object)) return false; @@ -493,7 +514,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiArrayBinding *node) const int propertyNameIndex = registerString(name->name.toString()); - if (bindingsTarget()->findBinding(propertyNameIndex) != 0) { + if (bindingsTarget()->findBinding(propertyNameIndex) != nullptr) { recordError(name->identifierToken, tr("Property value set multiple times")); return false; } @@ -568,7 +589,7 @@ bool IRBuilder::defineQMLObject(int *objectIndex, QQmlJS::AST::UiQualifiedId *qu _object->declarationsOverride = declarationsOverride; // A new object is also a boundary for property declarations. - Property *declaration = 0; + Property *declaration = nullptr; qSwap(_propertyDeclaration, declaration); accept(initializer); @@ -581,7 +602,7 @@ bool IRBuilder::defineQMLObject(int *objectIndex, QQmlJS::AST::UiQualifiedId *qu return false; QQmlJS::AST::SourceLocation loc; - QString error = obj->sanityCheckFunctionNames(illegalNames, &loc); + QString error = sanityCheckFunctionNames(obj, illegalNames, &loc); if (!error.isEmpty()) { recordError(loc, error); return false; @@ -598,7 +619,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiImport *node) if (!node->fileName.isNull()) { uri = node->fileName.toString(); - if (uri.endsWith(QLatin1String(".js"))) { + if (uri.endsWith(QLatin1String(".js")) || uri.endsWith(QLatin1String(".mjs"))) { import->type = QV4::CompiledData::Import::ImportScript; } else { import->type = QV4::CompiledData::Import::ImportFile; @@ -670,9 +691,9 @@ bool IRBuilder::visit(QQmlJS::AST::UiPragma *node) Pragma *pragma = New<Pragma>(); // For now the only valid pragma is Singleton, so lets validate the input - if (!node->pragmaType->name.isNull()) + if (!node->name.isNull()) { - if (QLatin1String("Singleton") == node->pragmaType->name) + if (QLatin1String("Singleton") == node->name) { pragma->type = Pragma::PragmaSingleton; } else { @@ -709,6 +730,52 @@ static QStringList astNodeToStringList(QQmlJS::AST::Node *node) return QStringList(); } +bool IRBuilder::visit(QQmlJS::AST::UiEnumDeclaration *node) +{ + Enum *enumeration = New<Enum>(); + QString enumName = node->name.toString(); + enumeration->nameIndex = registerString(enumName); + + if (enumName.at(0).isLower()) + COMPILE_EXCEPTION(node->enumToken, tr("Scoped enum names must begin with an upper case letter")); + + enumeration->location.line = node->enumToken.startLine; + enumeration->location.column = node->enumToken.startColumn; + + enumeration->enumValues = New<PoolList<EnumValue>>(); + + QQmlJS::AST::UiEnumMemberList *e = node->members; + while (e) { + EnumValue *enumValue = New<EnumValue>(); + QString member = e->member.toString(); + enumValue->nameIndex = registerString(member); + if (member.at(0).isLower()) + COMPILE_EXCEPTION(e->memberToken, tr("Enum names must begin with an upper case letter")); + + double part; + if (std::modf(e->value, &part) != 0.0) + COMPILE_EXCEPTION(e->valueToken, tr("Enum value must be an integer")); + if (e->value > std::numeric_limits<qint32>::max() || e->value < std::numeric_limits<qint32>::min()) + COMPILE_EXCEPTION(e->valueToken, tr("Enum value out of range")); + enumValue->value = e->value; + + enumValue->location.line = e->memberToken.startLine; + enumValue->location.column = e->memberToken.startColumn; + enumeration->enumValues->append(enumValue); + + e = e->next; + } + + QString error = _object->appendEnum(enumeration); + if (!error.isEmpty()) { + recordError(node->enumToken, error); + return false; + } + + return false; +} + + bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) { static const struct TypeNameToType { @@ -765,7 +832,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) return false; } - const TypeNameToType *type = 0; + const TypeNameToType *type = nullptr; for (int typeIndex = 0; typeIndex < propTypeNameToTypesCount; ++typeIndex) { const TypeNameToType *t = propTypeNameToTypes + typeIndex; if (memberType == QLatin1String(t->name, static_cast<int>(t->nameLength))) { @@ -891,7 +958,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) 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); + appendBinding(node->identifierToken, node->identifierToken, _propertyDeclaration->nameIndex, node->statement, node); } qSwap(_propertyDeclaration, property); } @@ -905,26 +972,27 @@ bool IRBuilder::visit(QQmlJS::AST::UiSourceElement *node) if (QQmlJS::AST::FunctionDeclaration *funDecl = QQmlJS::AST::cast<QQmlJS::AST::FunctionDeclaration *>(node->sourceElement)) { CompiledFunctionOrExpression *foe = New<CompiledFunctionOrExpression>(); foe->node = funDecl; + foe->parentNode = funDecl; foe->nameIndex = registerString(funDecl->name.toString()); foe->disableAcceleratedLookups = false; const int index = _object->functionsAndExpressions->append(foe); Function *f = New<Function>(); - f->functionDeclaration = funDecl; QQmlJS::AST::SourceLocation loc = funDecl->identifierToken; f->location.line = loc.startLine; 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; + const QStringList formals = funDecl->formals ? funDecl->formals->formals() : QStringList(); + int formalsCount = formals.size(); 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()); + for (const QString &arg : formals) { + f->formals[i] = registerString(arg); + ++i; + } _object->appendFunction(f); } else { @@ -978,7 +1046,7 @@ QStringRef IRBuilder::textRefAt(const QQmlJS::AST::SourceLocation &first, const return QStringRef(&sourceCode, first.offset, last.offset + last.length - first.offset); } -void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST::Statement *statement) +void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST::Statement *statement, QQmlJS::AST::Node *parentNode) { QQmlJS::AST::SourceLocation loc = statement->firstSourceLocation(); binding->valueLocation.line = loc.startLine; @@ -1001,15 +1069,23 @@ 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->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->setNumberValueInternal(-lit->value); - } + binding->value.constantValueIndex = jsGenerator->registerConstant(QV4::Encode(lit->value)); + } else if (QQmlJS::AST::CallExpression *call = QQmlJS::AST::cast<QQmlJS::AST::CallExpression *>(expr)) { + if (QQmlJS::AST::IdentifierExpression *base = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(call->base)) { + tryGeneratingTranslationBinding(base->name, call->arguments, binding); + // If it wasn't a translation binding, a normal script binding will be generated + // below. } + } else if (QQmlJS::AST::cast<QQmlJS::AST::FunctionExpression *>(expr)) { + binding->flags |= QV4::CompiledData::Binding::IsFunctionExpression; + } 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.constantValueIndex = jsGenerator->registerConstant(QV4::Encode(-lit->value)); + } + } else if (QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr)) { + binding->type = QV4::CompiledData::Binding::Type_Null; + binding->value.nullMarker = 0; } } @@ -1019,6 +1095,7 @@ void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST CompiledFunctionOrExpression *expr = New<CompiledFunctionOrExpression>(); expr->node = statement; + expr->parentNode = parentNode; expr->nameIndex = registerString(QLatin1String("expression for ") + stringAt(binding->propertyNameIndex)); expr->disableAcceleratedLookups = false; @@ -1030,10 +1107,125 @@ void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST } } -void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, QQmlJS::AST::Statement *value) +void IRBuilder::tryGeneratingTranslationBinding(const QStringRef &base, AST::ArgumentList *args, QV4::CompiledData::Binding *binding) +{ + if (base == QLatin1String("qsTr")) { + QV4::CompiledData::TranslationData translationData; + translationData.number = -1; + translationData.commentIndex = 0; // empty string + + if (!args || !args->expression) + return; // no arguments, stop + + QStringRef translation; + if (QQmlJS::AST::StringLiteral *arg1 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { + translation = arg1->value; + } else { + return; // first argument is not a string, stop + } + translationData.stringIndex = jsGenerator->registerString(translation.toString()); + + args = args->next; + + if (args) { + QQmlJS::AST::StringLiteral *arg2 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression); + if (!arg2) + return; // second argument is not a string, stop + translationData.commentIndex = jsGenerator->registerString(arg2->value.toString()); + + args = args->next; + if (args) { + if (QQmlJS::AST::NumericLiteral *arg3 = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(args->expression)) { + translationData.number = int(arg3->value); + args = args->next; + } else { + return; // third argument is not a translation number, stop + } + } + } + + if (args) + return; // too many arguments, stop + + binding->type = QV4::CompiledData::Binding::Type_Translation; + binding->value.translationDataIndex = jsGenerator->registerTranslation(translationData); + } else if (base == QLatin1String("qsTrId")) { + QV4::CompiledData::TranslationData translationData; + translationData.number = -1; + translationData.commentIndex = 0; // empty string, but unused + + if (!args || !args->expression) + return; // no arguments, stop + + QStringRef id; + if (QQmlJS::AST::StringLiteral *arg1 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { + id = arg1->value; + } else { + return; // first argument is not a string, stop + } + translationData.stringIndex = jsGenerator->registerString(id.toString()); + + args = args->next; + + if (args) { + if (QQmlJS::AST::NumericLiteral *arg3 = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(args->expression)) { + translationData.number = int(arg3->value); + args = args->next; + } else { + return; // third argument is not a translation number, stop + } + } + + if (args) + return; // too many arguments, stop + + binding->type = QV4::CompiledData::Binding::Type_TranslationById; + binding->value.translationDataIndex = jsGenerator->registerTranslation(translationData); + } else if (base == QLatin1String("QT_TR_NOOP") || base == QLatin1String("QT_TRID_NOOP")) { + if (!args || !args->expression) + return; // no arguments, stop + + QStringRef str; + if (QQmlJS::AST::StringLiteral *arg1 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { + str = arg1->value; + } else { + return; // first argument is not a string, stop + } + + args = args->next; + if (args) + return; // too many arguments, stop + + binding->type = QV4::CompiledData::Binding::Type_String; + binding->stringIndex = jsGenerator->registerString(str.toString()); + } else if (base == QLatin1String("QT_TRANSLATE_NOOP")) { + if (!args || !args->expression) + return; // no arguments, stop + + args = args->next; + if (!args || !args->expression) + return; // no second arguments, stop + + QStringRef str; + if (QQmlJS::AST::StringLiteral *arg2 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { + str = arg2->value; + } else { + return; // first argument is not a string, stop + } + + args = args->next; + if (args) + return; // too many arguments, stop + + binding->type = QV4::CompiledData::Binding::Type_String; + binding->stringIndex = jsGenerator->registerString(str.toString()); + } +} + +void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, QQmlJS::AST::Statement *value, QQmlJS::AST::Node *parentNode) { const QQmlJS::AST::SourceLocation qualifiedNameLocation = name->identifierToken; - Object *object = 0; + Object *object = nullptr; if (!resolveQualifiedId(&name, &object)) return; if (_object == object && name->name == QLatin1String("id")) { @@ -1041,14 +1233,14 @@ void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, QQmlJS::AST::Sta return; } qSwap(_object, object); - appendBinding(qualifiedNameLocation, name->identifierToken, registerString(name->name.toString()), value); + appendBinding(qualifiedNameLocation, name->identifierToken, registerString(name->name.toString()), value, parentNode); qSwap(_object, object); } void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, int objectIndex, bool isOnAssignment) { const QQmlJS::AST::SourceLocation qualifiedNameLocation = name->identifierToken; - Object *object = 0; + Object *object = nullptr; if (!resolveQualifiedId(&name, &object, isOnAssignment)) return; qSwap(_object, object); @@ -1056,7 +1248,8 @@ void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, int objectIndex, qSwap(_object, object); } -void IRBuilder::appendBinding(const QQmlJS::AST::SourceLocation &qualifiedNameLocation, const QQmlJS::AST::SourceLocation &nameLocation, quint32 propertyNameIndex, QQmlJS::AST::Statement *value) +void IRBuilder::appendBinding(const QQmlJS::AST::SourceLocation &qualifiedNameLocation, const QQmlJS::AST::SourceLocation &nameLocation, quint32 propertyNameIndex, + QQmlJS::AST::Statement *value, QQmlJS::AST::Node *parentNode) { Binding *binding = New<Binding>(); binding->propertyNameIndex = propertyNameIndex; @@ -1064,7 +1257,7 @@ void IRBuilder::appendBinding(const QQmlJS::AST::SourceLocation &qualifiedNameLo binding->location.line = nameLocation.startLine; binding->location.column = nameLocation.startColumn; binding->flags = 0; - setBindingValue(binding, value); + setBindingValue(binding, value, parentNode); QString error = bindingsTarget()->appendBinding(binding, /*isListBinding*/false); if (!error.isEmpty()) { recordError(qualifiedNameLocation, error); @@ -1199,7 +1392,7 @@ bool IRBuilder::setId(const QQmlJS::AST::SourceLocation &idLocation, QQmlJS::AST if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(node)) { if (QQmlJS::AST::StringLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(stmt->expression)) { str = lit->value; - node = 0; + node = nullptr; } else node = stmt->expression; } @@ -1270,9 +1463,9 @@ bool IRBuilder::resolveQualifiedId(QQmlJS::AST::UiQualifiedId **nameToResolve, O if (binding) { if (isAttachedProperty) { if (!binding->isAttachedProperty()) - binding = 0; + binding = nullptr; } else if (!binding->isGroupProperty()) { - binding = 0; + binding = nullptr; } } if (!binding) { @@ -1294,7 +1487,7 @@ bool IRBuilder::resolveQualifiedId(QQmlJS::AST::UiQualifiedId **nameToResolve, O binding->type = QV4::CompiledData::Binding::Type_GroupProperty; int objIndex = 0; - if (!defineQMLObject(&objIndex, 0, QQmlJS::AST::SourceLocation(), 0, 0)) + if (!defineQMLObject(&objIndex, nullptr, QQmlJS::AST::SourceLocation(), nullptr, nullptr)) return false; binding->value.objectIndex = objIndex; @@ -1361,60 +1554,105 @@ bool IRBuilder::isRedundantNullInitializerForPropertyDeclaration(Property *prope return QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr); } -QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::DependentTypesHasher &dependencyHasher) +void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::DependentTypesHasher &dependencyHasher) { + output.jsGenerator.stringTable.registerString(output.jsModule.fileName); + output.jsGenerator.stringTable.registerString(output.jsModule.finalUrl); + QQmlRefPointer<QV4::CompiledData::CompilationUnit> compilationUnit = output.javaScriptCompilationUnit; - QV4::CompiledData::Unit *jsUnit = compilationUnit->createUnitData(&output); - const uint unitSize = jsUnit->unitSize; - const int importSize = sizeof(QV4::CompiledData::Import) * output.imports.count(); - const int objectOffsetTableSize = output.objects.count() * sizeof(quint32); + const QV4::CompiledData::Unit *jsUnit = nullptr; + std::function<QV4::CompiledData::QmlUnit *(QV4::CompiledData::QmlUnit *, uint)> unitFinalizer + = [](QV4::CompiledData::QmlUnit *unit, uint) { + return unit; + }; + + // We may already have unit data if we're loading an ahead-of-time generated cache file. + if (compilationUnit->data) { + jsUnit = compilationUnit->data; +#ifndef V4_BOOTSTRAP + output.javaScriptCompilationUnit->dynamicStrings = output.jsGenerator.stringTable.allStrings(); +#endif + } else { + QV4::CompiledData::Unit *createdUnit; + jsUnit = createdUnit = output.jsGenerator.generateUnit(); + + // enable flag if we encountered pragma Singleton + for (Pragma *p : qAsConst(output.pragmas)) { + if (p->type == Pragma::PragmaSingleton) { + createdUnit->flags |= QV4::CompiledData::Unit::IsSingleton; + break; + } + } + // This unit's memory was allocated with malloc on the heap, so it's + // definitely not suitable for StaticData access. + createdUnit->flags &= ~QV4::CompiledData::Unit::StaticData; + +#ifndef V4_BOOTSTRAP + if (dependencyHasher) { + QCryptographicHash hash(QCryptographicHash::Md5); + if (dependencyHasher(&hash)) { + QByteArray checksum = hash.result(); + Q_ASSERT(checksum.size() == sizeof(createdUnit->dependencyMD5Checksum)); + memcpy(createdUnit->dependencyMD5Checksum, checksum.constData(), sizeof(createdUnit->dependencyMD5Checksum)); + } + } +#else + Q_UNUSED(dependencyHasher); +#endif + createdUnit->sourceFileIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.fileName); + createdUnit->finalUrlIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.finalUrl); + + // Combine the qml data into the general unit data. + unitFinalizer = [&jsUnit](QV4::CompiledData::QmlUnit *qmlUnit, uint qmlDataSize) { + void *ptr = const_cast<QV4::CompiledData::Unit*>(jsUnit); + QV4::CompiledData::Unit *newUnit = (QV4::CompiledData::Unit *)realloc(ptr, jsUnit->unitSize + qmlDataSize); + jsUnit = newUnit; + newUnit->offsetToQmlUnit = newUnit->unitSize; + newUnit->unitSize += qmlDataSize; + memcpy(const_cast<QV4::CompiledData::QmlUnit *>(newUnit->qmlUnit()), qmlUnit, qmlDataSize); + free(const_cast<QV4::CompiledData::QmlUnit*>(qmlUnit)); + qmlUnit = nullptr; + newUnit->generateChecksum(); + return const_cast<QV4::CompiledData::QmlUnit*>(newUnit->qmlUnit()); + }; + } + + // No more new strings after this point, we're calculating offsets. + output.jsGenerator.stringTable.freeze(); + + const uint importSize = sizeof(QV4::CompiledData::Import) * output.imports.count(); + const uint objectOffsetTableSize = output.objects.count() * sizeof(quint32); QHash<const Object*, quint32> objectOffsets; - int objectsSize = 0; + const unsigned int objectOffset = sizeof(QV4::CompiledData::QmlUnit) + importSize; + uint nextOffset = objectOffset + objectOffsetTableSize; for (Object *o : qAsConst(output.objects)) { - objectOffsets.insert(o, unitSize + importSize + objectOffsetTableSize + objectsSize); - objectsSize += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functionCount(), o->propertyCount(), o->aliasCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.count); + objectOffsets.insert(o, nextOffset); + nextOffset += QV4::CompiledData::Object::calculateSizeExcludingSignalsAndEnums(o->functionCount(), o->propertyCount(), o->aliasCount(), o->enumCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.size()); int signalTableSize = 0; for (const Signal *s = o->firstSignal(); s; s = s->next) signalTableSize += QV4::CompiledData::Signal::calculateSize(s->parameters->count); - objectsSize += signalTableSize; + nextOffset += signalTableSize; + + int enumTableSize = 0; + for (const Enum *e = o->firstEnum(); e; e = e->next) + enumTableSize += QV4::CompiledData::Enum::calculateSize(e->enumValues->count); + + nextOffset += enumTableSize; } - const int totalSize = unitSize + importSize + objectOffsetTableSize + objectsSize + output.jsGenerator.stringTable.sizeOfTableAndData(); + const uint totalSize = nextOffset; char *data = (char*)malloc(totalSize); - memcpy(data, jsUnit, unitSize); - memset(data + unitSize, 0, totalSize - unitSize); - if (jsUnit != compilationUnit->data) - free(jsUnit); - jsUnit = 0; - - QV4::CompiledData::Unit *qmlUnit = reinterpret_cast<QV4::CompiledData::Unit *>(data); - qmlUnit->unitSize = totalSize; - qmlUnit->flags |= QV4::CompiledData::Unit::IsQml; - qmlUnit->offsetToImports = unitSize; + memset(data, 0, totalSize); + QV4::CompiledData::QmlUnit *qmlUnit = reinterpret_cast<QV4::CompiledData::QmlUnit *>(data); + qmlUnit->offsetToImports = sizeof(*qmlUnit); qmlUnit->nImports = output.imports.count(); - qmlUnit->offsetToObjects = unitSize + importSize; + qmlUnit->offsetToObjects = objectOffset; qmlUnit->nObjects = output.objects.count(); - qmlUnit->indexOfRootObject = output.indexOfRootObject; - qmlUnit->offsetToStringTable = totalSize - output.jsGenerator.stringTable.sizeOfTableAndData(); - qmlUnit->stringTableSize = output.jsGenerator.stringTable.stringCount(); - -#ifndef V4_BOOTSTRAP - if (dependencyHasher) { - QCryptographicHash hash(QCryptographicHash::Md5); - if (dependencyHasher(&hash)) { - QByteArray checksum = hash.result(); - Q_ASSERT(checksum.size() == sizeof(qmlUnit->dependencyMD5Checksum)); - memcpy(qmlUnit->dependencyMD5Checksum, checksum.constData(), sizeof(qmlUnit->dependencyMD5Checksum)); - } - } -#else - Q_UNUSED(dependencyHasher); -#endif // write imports char *importPtr = data + qmlUnit->offsetToImports; @@ -1425,10 +1663,10 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, const QV4: } // write objects - quint32 *objectTable = reinterpret_cast<quint32*>(data + qmlUnit->offsetToObjects); - char *objectPtr = data + qmlUnit->offsetToObjects + objectOffsetTableSize; + quint32_le *objectTable = reinterpret_cast<quint32_le*>(data + qmlUnit->offsetToObjects); for (int i = 0; i < output.objects.count(); ++i) { const Object *o = output.objects.at(i); + char * const objectPtr = data + objectOffsets.value(o); *objectTable++ = objectOffsets.value(o); QV4::CompiledData::Object *objectToWrite = reinterpret_cast<QV4::CompiledData::Object*>(objectPtr); @@ -1455,6 +1693,10 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, const QV4: objectToWrite->offsetToAliases = nextOffset; nextOffset += objectToWrite->nAliases * sizeof(QV4::CompiledData::Alias); + objectToWrite->nEnums = o->enumCount(); + objectToWrite->offsetToEnums = nextOffset; + nextOffset += objectToWrite->nEnums * sizeof(quint32); + objectToWrite->nSignals = o->signalCount(); objectToWrite->offsetToSignals = nextOffset; nextOffset += objectToWrite->nSignals * sizeof(quint32); @@ -1463,11 +1705,11 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, const QV4: objectToWrite->offsetToBindings = nextOffset; nextOffset += objectToWrite->nBindings * sizeof(QV4::CompiledData::Binding); - objectToWrite->nNamedObjectsInComponent = o->namedObjectsInComponent.count; + objectToWrite->nNamedObjectsInComponent = o->namedObjectsInComponent.size(); objectToWrite->offsetToNamedObjectsInComponent = nextOffset; nextOffset += objectToWrite->nNamedObjectsInComponent * sizeof(quint32); - quint32 *functionsTable = reinterpret_cast<quint32*>(objectPtr + objectToWrite->offsetToFunctions); + quint32_le *functionsTable = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToFunctions); for (const Function *f = o->firstFunction(); f; f = f->next) *functionsTable++ = o->runtimeFunctionIndices.at(f->index); @@ -1493,7 +1735,7 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, const QV4: bindingPtr = writeBindings(bindingPtr, o, &QV4::CompiledData::Binding::isValueBindingToAlias); Q_ASSERT((bindingPtr - objectToWrite->offsetToBindings - objectPtr) / sizeof(QV4::CompiledData::Binding) == unsigned(o->bindingCount())); - quint32 *signalOffsetTable = reinterpret_cast<quint32*>(objectPtr + objectToWrite->offsetToSignals); + quint32_le *signalOffsetTable = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToSignals); quint32 signalTableSize = 0; char *signalPtr = objectPtr + nextOffset; for (const Signal *s = o->firstSignal(); s; s = s->next) { @@ -1512,29 +1754,59 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, const QV4: signalTableSize += size; signalPtr += size; } - - quint32 *namedObjectInComponentPtr = reinterpret_cast<quint32*>(objectPtr + objectToWrite->offsetToNamedObjectsInComponent); - for (int i = 0; i < o->namedObjectsInComponent.count; ++i) { - *namedObjectInComponentPtr++ = o->namedObjectsInComponent.at(i); + nextOffset += signalTableSize; + + quint32_le *enumOffsetTable = reinterpret_cast<quint32_le*>(objectPtr + objectToWrite->offsetToEnums); + quint32 enumTableSize = 0; + char *enumPtr = objectPtr + nextOffset; + for (const Enum *e = o->firstEnum(); e; e = e->next) { + *enumOffsetTable++ = enumPtr - objectPtr; + QV4::CompiledData::Enum *enumToWrite = reinterpret_cast<QV4::CompiledData::Enum*>(enumPtr); + + enumToWrite->nameIndex = e->nameIndex; + enumToWrite->location = e->location; + enumToWrite->nEnumValues = e->enumValues->count; + + QV4::CompiledData::EnumValue *enumValueToWrite = reinterpret_cast<QV4::CompiledData::EnumValue*>(enumPtr + sizeof(*enumToWrite)); + for (EnumValue *enumValue = e->enumValues->first; enumValue; enumValue = enumValue->next, ++enumValueToWrite) + *enumValueToWrite = *enumValue; + + int size = QV4::CompiledData::Enum::calculateSize(e->enumValues->count); + enumTableSize += size; + enumPtr += size; } - objectPtr += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functionCount(), o->propertyCount(), o->aliasCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.count); - objectPtr += signalTableSize; - } - - // enable flag if we encountered pragma Singleton - for (Pragma *p : qAsConst(output.pragmas)) { - if (p->type == Pragma::PragmaSingleton) { - qmlUnit->flags |= QV4::CompiledData::Unit::IsSingleton; - break; + quint32_le *namedObjectInComponentPtr = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToNamedObjectsInComponent); + for (int i = 0; i < o->namedObjectsInComponent.size(); ++i) { + *namedObjectInComponentPtr++ = o->namedObjectsInComponent.at(i); } } - output.jsGenerator.stringTable.serialize(qmlUnit); + qmlUnit = unitFinalizer(qmlUnit, totalSize); - qmlUnit->generateChecksum(); + static const bool showStats = qEnvironmentVariableIsSet("QML_SHOW_UNIT_STATS"); + if (showStats) { + qDebug() << "Generated QML unit that is" << totalSize << "bytes big contains:"; + qDebug() << " " << jsUnit->functionTableSize << "functions"; + qDebug() << " " << jsUnit->unitSize << "for JS unit"; + qDebug() << " " << importSize << "for imports"; + qDebug() << " " << nextOffset - objectOffset - objectOffsetTableSize << "for" << qmlUnit->nObjects << "objects"; + quint32 totalBindingCount = 0; + for (quint32 i = 0; i < qmlUnit->nObjects; ++i) + totalBindingCount += qmlUnit->objectAt(i)->nBindings; + qDebug() << " " << totalBindingCount << "bindings"; + quint32 totalCodeSize = 0; + for (quint32 i = 0; i < jsUnit->functionTableSize; ++i) + totalCodeSize += jsUnit->functionAt(i)->codeSize; + qDebug() << " " << totalCodeSize << "bytes total byte code"; + qDebug() << " " << jsUnit->stringTableSize << "strings"; + quint32 totalStringSize = 0; + for (quint32 i = 0; i < jsUnit->stringTableSize; ++i) + totalStringSize += QV4::CompiledData::String::calculateSize(jsUnit->stringAtInternal(i)); + qDebug() << " " << totalStringSize << "bytes total strings"; + } - return qmlUnit; + compilationUnit->setUnitData(jsUnit, qmlUnit, output.jsModule.fileName, output.jsModule.finalUrl); } char *QmlUnitGenerator::writeBindings(char *bindingPtr, const Object *o, BindingFilter filter) const @@ -1551,22 +1823,25 @@ char *QmlUnitGenerator::writeBindings(char *bindingPtr, const Object *o, Binding return bindingPtr; } -JSCodeGen::JSCodeGen(const QString &fileName, const QString &sourceCode, QV4::IR::Module *jsModule, QQmlJS::Engine *jsEngine, - QQmlJS::AST::UiProgram *qmlRoot, QQmlTypeNameCache *imports, const QV4::Compiler::StringTableGenerator *stringPool) - : QQmlJS::Codegen(/*strict mode*/false) +JSCodeGen::JSCodeGen(const QString &sourceCode, QV4::Compiler::JSUnitGenerator *jsUnitGenerator, + QV4::Compiler::Module *jsModule, QQmlJS::Engine *jsEngine, + QQmlJS::AST::UiProgram *qmlRoot, QQmlTypeNameCache *imports, + const QV4::Compiler::StringTableGenerator *stringPool, const QSet<QString> &globalNames) + : QV4::Compiler::Codegen(jsUnitGenerator, /*strict mode*/false) , sourceCode(sourceCode) , jsEngine(jsEngine) , qmlRoot(qmlRoot) , imports(imports) , stringPool(stringPool) , _disableAcceleratedLookups(false) - , _contextObject(0) - , _scopeObject(0) - , _qmlContextTemp(-1) - , _importedScriptsTemp(-1) + , _contextObject(nullptr) + , _scopeObject(nullptr) + , _qmlContextSlot(-1) + , _importedScriptsSlot(-1) { + m_globalNames = globalNames; + _module = jsModule; - _module->setFileName(fileName); _fileNameIsUrl = true; } @@ -1574,7 +1849,7 @@ void JSCodeGen::beginContextScope(const JSCodeGen::ObjectIdMapping &objectIds, Q { _idObjects = objectIds; _contextObject = contextObject; - _scopeObject = 0; + _scopeObject = nullptr; } void JSCodeGen::beginObjectScope(QQmlPropertyCache *scopeObject) @@ -1584,28 +1859,37 @@ void JSCodeGen::beginObjectScope(QQmlPropertyCache *scopeObject) QVector<int> JSCodeGen::generateJSCodeForFunctionsAndBindings(const QList<CompiledFunctionOrExpression> &functions) { + auto qmlName = [&](const CompiledFunctionOrExpression &c) { + if (c.nameIndex != 0) + return stringPool->stringForIndex(c.nameIndex); + else + return QStringLiteral("%qml-expression-entry"); + }; QVector<int> runtimeFunctionIndices(functions.size()); - ScanFunctions scan(this, sourceCode, GlobalCode); - scan.enterEnvironment(0, QmlBinding); - scan.enterQmlScope(qmlRoot, QStringLiteral("context scope")); + QV4::Compiler::ScanFunctions scan(this, sourceCode, QV4::Compiler::ContextType::Global); + scan.enterGlobalEnvironment(QV4::Compiler::ContextType::Binding); for (const CompiledFunctionOrExpression &f : functions) { Q_ASSERT(f.node != qmlRoot); + Q_ASSERT(f.parentNode && f.parentNode != qmlRoot); QQmlJS::AST::FunctionDeclaration *function = QQmlJS::AST::cast<QQmlJS::AST::FunctionDeclaration*>(f.node); - if (function) + if (function) { scan.enterQmlFunction(function); - else - scan.enterEnvironment(f.node, QmlBinding); + } else { + Q_ASSERT(f.node != f.parentNode); + scan.enterEnvironment(f.parentNode, QV4::Compiler::ContextType::Binding, qmlName(f)); + } scan(function ? function->body : f.node); scan.leaveEnvironment(); } scan.leaveEnvironment(); - scan.leaveEnvironment(); - _variableEnvironment = 0; - _function = _module->functions.at(defineFunction(QStringLiteral("context scope"), qmlRoot, 0, 0)); + if (hasError) + return QVector<int>(); + + _context = nullptr; for (int i = 0; i < functions.count(); ++i) { const CompiledFunctionOrExpression &qmlFunction = functions.at(i); @@ -1617,15 +1901,13 @@ QVector<int> JSCodeGen::generateJSCodeForFunctionsAndBindings(const QList<Compil QString name; if (function) name = function->name.toString(); - else if (qmlFunction.nameIndex != 0) - name = stringPool->stringForIndex(qmlFunction.nameIndex); else - name = QStringLiteral("%qml-expression-entry"); + name = qmlName(qmlFunction); - QQmlJS::AST::SourceElements *body; - if (function) - body = function->body ? function->body->elements : 0; - else { + QQmlJS::AST::StatementList *body; + if (function) { + body = function->body; + } else { // Synthesize source elements. QQmlJS::MemoryPool *pool = jsEngine->pool(); @@ -1635,47 +1917,43 @@ QVector<int> JSCodeGen::generateJSCodeForFunctionsAndBindings(const QList<Compil QQmlJS::AST::ExpressionNode *expr = node->expressionCast(); stmt = new (pool) QQmlJS::AST::ExpressionStatement(expr); } - QQmlJS::AST::SourceElement *element = new (pool) QQmlJS::AST::StatementSourceElement(stmt); - body = new (pool) QQmlJS::AST::SourceElements(element); + body = new (pool) QQmlJS::AST::StatementList(stmt); body = body->finish(); } _disableAcceleratedLookups = qmlFunction.disableAcceleratedLookups; - int idx = defineFunction(name, node, - function ? function->formals : 0, + int idx = defineFunction(name, function ? function : qmlFunction.parentNode, + function ? function->formals : nullptr, body); runtimeFunctionIndices[i] = idx; } - qDeleteAll(_envMap); - _envMap.clear(); return runtimeFunctionIndices; } -#ifndef V4_BOOTSTRAP -QQmlPropertyData *JSCodeGen::lookupQmlCompliantProperty(QQmlPropertyCache *cache, const QString &name, bool *propertyExistsButForceNameLookup) +int JSCodeGen::defineFunction(const QString &name, AST::Node *ast, AST::FormalParameterList *formals, AST::StatementList *body) { - if (propertyExistsButForceNameLookup) - *propertyExistsButForceNameLookup = false; - QQmlPropertyData *pd = cache->property(name, /*object*/0, /*context*/0); + int qmlContextTemp = -1; + int importedScriptsTemp = -1; + qSwap(_qmlContextSlot, qmlContextTemp); + qSwap(_importedScriptsSlot, importedScriptsTemp); - // Q_INVOKABLEs can't be FINAL, so we have to look them up at run-time - if (pd && pd->isFunction()) { - if (propertyExistsButForceNameLookup) - *propertyExistsButForceNameLookup = true; - pd = 0; - } + int result = Codegen::defineFunction(name, ast, formals, body); + + qSwap(_importedScriptsSlot, importedScriptsTemp); + qSwap(_qmlContextSlot, qmlContextTemp); + + return result; +} + +#ifndef V4_BOOTSTRAP +QQmlPropertyData *JSCodeGen::lookupQmlCompliantProperty(QQmlPropertyCache *cache, const QString &name) +{ + QQmlPropertyData *pd = cache->property(name, /*object*/nullptr, /*context*/nullptr); if (pd && !cache->isAllowedInRevision(pd)) - pd = 0; + return nullptr; - // Return a copy allocated from our memory pool. Property data pointers can change - // otherwise when the QQmlPropertyCache changes later in the QML type compilation process. - if (pd) { - QQmlPropertyData *copy = pd; - pd = _function->New<QQmlPropertyData>(); - *pd = *copy; - } return pd; } @@ -1686,8 +1964,10 @@ enum MetaObjectResolverFlags { ResolveTypeInformationOnly = 0x8 }; +#if 0 static void initMetaObjectResolver(QV4::IR::MemberExpressionResolver *resolver, QQmlPropertyCache *metaObject); -static void initScopedEnumResolver(QV4::IR::MemberExpressionResolver *resolver, QQmlType *qmlType, int index); + +static void initScopedEnumResolver(QV4::IR::MemberExpressionResolver *resolver, const QQmlType &qmlType, int index); static QV4::IR::DiscoveredType resolveQmlType(QQmlEnginePrivate *qmlEngine, const QV4::IR::MemberExpressionResolver *resolver, @@ -1695,16 +1975,16 @@ static QV4::IR::DiscoveredType resolveQmlType(QQmlEnginePrivate *qmlEngine, { QV4::IR::Type result = QV4::IR::VarType; - QQmlType *type = static_cast<QQmlType*>(resolver->data); + QQmlType type = resolver->qmlType; if (member->name->constData()->isUpper()) { bool ok = false; - int value = type->enumValue(qmlEngine, *member->name, &ok); + int value = type.enumValue(qmlEngine, *member->name, &ok); if (ok) { member->setEnumValue(value); return QV4::IR::SInt32Type; } else { - int index = type->scopedEnumIndex(qmlEngine, *member->name, &ok); + int index = type.scopedEnumIndex(qmlEngine, *member->name, &ok); if (ok) { auto newResolver = resolver->owner->New<QV4::IR::MemberExpressionResolver>(); newResolver->owner = resolver->owner; @@ -1714,8 +1994,8 @@ static QV4::IR::DiscoveredType resolveQmlType(QQmlEnginePrivate *qmlEngine, } } - if (type->isCompositeSingleton()) { - QQmlRefPointer<QQmlTypeData> tdata = qmlEngine->typeLoader.getType(type->singletonInstanceInfo()->url); + if (type.isCompositeSingleton()) { + QQmlRefPointer<QQmlTypeData> tdata = qmlEngine->typeLoader.getType(type.singletonInstanceInfo()->url); Q_ASSERT(tdata); tdata->release(); // Decrease the reference count added from QQmlTypeLoader::getType() // When a singleton tries to reference itself, it may not be complete yet. @@ -1726,8 +2006,8 @@ static QV4::IR::DiscoveredType resolveQmlType(QQmlEnginePrivate *qmlEngine, newResolver->flags |= AllPropertiesAreFinal; return newResolver->resolveMember(qmlEngine, newResolver, member); } - } else if (type->isSingleton()) { - const QMetaObject *singletonMeta = type->singletonInstanceInfo()->instanceMetaObject; + } else if (type.isSingleton()) { + const QMetaObject *singletonMeta = type.singletonInstanceInfo()->instanceMetaObject; if (singletonMeta) { // QJSValue-based singletons cannot be accelerated auto newResolver = resolver->owner->New<QV4::IR::MemberExpressionResolver>(); newResolver->owner = resolver->owner; @@ -1752,13 +2032,13 @@ static QV4::IR::DiscoveredType resolveQmlType(QQmlEnginePrivate *qmlEngine, return result; } -static void initQmlTypeResolver(QV4::IR::MemberExpressionResolver *resolver, QQmlType *qmlType) +static void initQmlTypeResolver(QV4::IR::MemberExpressionResolver *resolver, const QQmlType &qmlType) { Q_ASSERT(resolver); resolver->resolveMember = &resolveQmlType; - resolver->data = qmlType; - resolver->extraData = 0; + resolver->qmlType = qmlType; + resolver->typenameCache = 0; resolver->flags = 0; } @@ -1767,8 +2047,8 @@ static QV4::IR::DiscoveredType resolveImportNamespace( QV4::IR::Member *member) { QV4::IR::Type result = QV4::IR::VarType; - QQmlTypeNameCache *typeNamespace = static_cast<QQmlTypeNameCache*>(resolver->extraData); - void *importNamespace = resolver->data; + QQmlTypeNameCache *typeNamespace = resolver->typenameCache; + const QQmlImportRef *importNamespace = resolver->import; QQmlTypeNameCache::Result r = typeNamespace->query(*member->name, importNamespace); if (r.isValid()) { @@ -1776,11 +2056,11 @@ static QV4::IR::DiscoveredType resolveImportNamespace( if (r.scriptIndex != -1) { // TODO: remember the index and replace with subscript later. result = QV4::IR::VarType; - } else if (r.type) { + } else if (r.type.isValid()) { // TODO: Propagate singleton information, so that it is loaded // through the singleton getter in the run-time. Until then we // can't accelerate access :( - if (!r.type->isSingleton()) { + if (!r.type.isSingleton()) { auto newResolver = resolver->owner->New<QV4::IR::MemberExpressionResolver>(); newResolver->owner = resolver->owner; initQmlTypeResolver(newResolver, r.type); @@ -1795,11 +2075,11 @@ static QV4::IR::DiscoveredType resolveImportNamespace( } static void initImportNamespaceResolver(QV4::IR::MemberExpressionResolver *resolver, - QQmlTypeNameCache *imports, const void *importNamespace) + QQmlTypeNameCache *imports, const QQmlImportRef *importNamespace) { resolver->resolveMember = &resolveImportNamespace; - resolver->data = const_cast<void*>(importNamespace); - resolver->extraData = imports; + resolver->import = importNamespace; + resolver->typenameCache = imports; resolver->flags = 0; } @@ -1808,7 +2088,7 @@ static QV4::IR::DiscoveredType resolveMetaObjectProperty( QV4::IR::Member *member) { QV4::IR::Type result = QV4::IR::VarType; - QQmlPropertyCache *metaObject = static_cast<QQmlPropertyCache*>(resolver->data); + QQmlPropertyCache *metaObject = resolver->propertyCache; if (member->name->constData()->isUpper() && (resolver->flags & LookupsIncludeEnums)) { const QMetaObject *mo = metaObject->createMetaObject(); @@ -1890,7 +2170,7 @@ static void initMetaObjectResolver(QV4::IR::MemberExpressionResolver *resolver, Q_ASSERT(resolver); resolver->resolveMember = &resolveMetaObjectProperty; - resolver->data = metaObject; + resolver->propertyCache = metaObject; resolver->flags = 0; } @@ -1901,54 +2181,59 @@ static QV4::IR::DiscoveredType resolveScopedEnum(QQmlEnginePrivate *qmlEngine, if (!member->name->constData()->isUpper()) return QV4::IR::VarType; - QQmlType *type = static_cast<QQmlType*>(resolver->data); + QQmlType type = resolver->qmlType; int index = resolver->flags; bool ok = false; - int value = type->scopedEnumValue(qmlEngine, index, *member->name, &ok); + int value = type.scopedEnumValue(qmlEngine, index, *member->name, &ok); if (!ok) return QV4::IR::VarType; member->setEnumValue(value); return QV4::IR::SInt32Type; } -static void initScopedEnumResolver(QV4::IR::MemberExpressionResolver *resolver, QQmlType *qmlType, int index) +static void initScopedEnumResolver(QV4::IR::MemberExpressionResolver *resolver, const QQmlType &qmlType, int index) { Q_ASSERT(resolver); resolver->resolveMember = &resolveScopedEnum; - resolver->data = qmlType; - resolver->extraData = 0; + resolver->qmlType = qmlType; resolver->flags = index; } +#endif #endif // V4_BOOTSTRAP void JSCodeGen::beginFunctionBodyHook() { - _qmlContextTemp = _block->newTemp(); - _importedScriptsTemp = _block->newTemp(); + _qmlContextSlot = bytecodeGenerator->newRegister(); + _importedScriptsSlot = bytecodeGenerator->newRegister(); #ifndef V4_BOOTSTRAP - QV4::IR::Temp *temp = _block->TEMP(_qmlContextTemp); + Instruction::LoadQmlContext load; + load.result = Reference::fromStackSlot(this, _qmlContextSlot).stackSlot(); + bytecodeGenerator->addInstruction(load); + +#if 0 temp->type = QV4::IR::QObjectType; temp->memberResolver = _function->New<QV4::IR::MemberExpressionResolver>(); initMetaObjectResolver(temp->memberResolver, _scopeObject); auto name = _block->NAME(QV4::IR::Name::builtin_qml_context, 0, 0); name->type = temp->type; - move(temp, name); +#endif - move(_block->TEMP(_importedScriptsTemp), _block->NAME(QV4::IR::Name::builtin_qml_imported_scripts_object, 0, 0)); + Instruction::LoadQmlImportedScripts loadScripts; + loadScripts.result = Reference::fromStackSlot(this, _importedScriptsSlot).stackSlot(); + bytecodeGenerator->addInstruction(loadScripts); #endif } -QV4::IR::Expr *JSCodeGen::fallbackNameLookup(const QString &name, int line, int col) +QV4::Compiler::Codegen::Reference JSCodeGen::fallbackNameLookup(const QString &name) { - Q_UNUSED(line) - Q_UNUSED(col) #ifndef V4_BOOTSTRAP if (_disableAcceleratedLookups) - return 0; + return Reference(); + // Implement QML lookup semantics in the current file context. // // Note: We do not check if properties of the qml scope object or context object @@ -1963,20 +2248,16 @@ QV4::IR::Expr *JSCodeGen::fallbackNameLookup(const QString &name, int line, int // Look for IDs first. for (const IdMapping &mapping : qAsConst(_idObjects)) { if (name == mapping.name) { - 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); - result = _block->TEMP(result->index); - if (mapping.type) { - result->memberResolver = _function->New<QV4::IR::MemberExpressionResolver>(); - result->memberResolver->owner = _function; - initMetaObjectResolver(result->memberResolver, mapping.type); - result->memberResolver->flags |= AllPropertiesAreFinal; - } - result->isReadOnly = true; // don't allow use as lvalue + if (_context->contextType == QV4::Compiler::ContextType::Binding) + _context->idObjectDependencies.insert(mapping.idIndex); + + Instruction::LoadIdObject load; + load.base = Reference::fromStackSlot(this, _qmlContextSlot).stackSlot(); + load.index = mapping.idIndex; + + Reference result = Reference::fromAccumulator(this); + bytecodeGenerator->addInstruction(load); + result.isReadonly = true; return result; } } @@ -1985,68 +2266,54 @@ QV4::IR::Expr *JSCodeGen::fallbackNameLookup(const QString &name, int line, int QQmlTypeNameCache::Result r = imports->query(name); if (r.isValid()) { if (r.scriptIndex != -1) { - return subscript(_block->TEMP(_importedScriptsTemp), _block->CONST(QV4::IR::SInt32Type, r.scriptIndex)); - } else if (r.type) { - QV4::IR::Name *typeName = _block->NAME(name, line, col); - // Make sure the run-time loads this through the more efficient singleton getter. - typeName->qmlSingleton = r.type->isCompositeSingleton(); - typeName->freeOfSideEffects = true; - QV4::IR::Temp *result = _block->TEMP(_block->newTemp()); - _block->MOVE(result, typeName); - - result = _block->TEMP(result->index); - result->memberResolver = _function->New<QV4::IR::MemberExpressionResolver>(); - result->memberResolver->owner = _function; - initQmlTypeResolver(result->memberResolver, r.type); - return result; + Reference imports = Reference::fromStackSlot(this, _importedScriptsSlot); + return Reference::fromSubscript(imports, Reference::fromConst(this, QV4::Encode(r.scriptIndex))); + } else if (r.type.isValid()) { + return Reference::fromName(this, name); } else { Q_ASSERT(r.importNamespace); - QV4::IR::Name *namespaceName = _block->NAME(name, line, col); - namespaceName->freeOfSideEffects = true; - QV4::IR::Temp *result = _block->TEMP(_block->newTemp()); - result->memberResolver = _function->New<QV4::IR::MemberExpressionResolver>(); - result->memberResolver->owner = _function; - initImportNamespaceResolver(result->memberResolver, imports, r.importNamespace); - - _block->MOVE(result, namespaceName); - return _block->TEMP(result->index); + return Reference::fromName(this, name); } } } if (_scopeObject) { - bool propertyExistsButForceNameLookup = false; - QQmlPropertyData *pd = lookupQmlCompliantProperty(_scopeObject, name, &propertyExistsButForceNameLookup); - if (propertyExistsButForceNameLookup) - return 0; - if (pd) { - QV4::IR::Temp *base = _block->TEMP(_qmlContextTemp); - base->memberResolver = _function->New<QV4::IR::MemberExpressionResolver>(); - base->memberResolver->owner = _function; - initMetaObjectResolver(base->memberResolver, _scopeObject); - return _block->MEMBER(base, _function->newString(name), pd, QV4::IR::Member::MemberOfQmlScopeObject); + QQmlPropertyData *data = lookupQmlCompliantProperty(_scopeObject, name); + if (data) { + // Q_INVOKABLEs can't be FINAL, so we have to look them up at run-time + if (data->isFunction()) + return Reference::fromName(this, name); + + Reference base = Reference::fromStackSlot(this, _qmlContextSlot); + Reference::PropertyCapturePolicy capturePolicy; + if (!data->isConstant() && !data->isQmlBinding()) + capturePolicy = Reference::CaptureAtRuntime; + else + capturePolicy = data->isConstant() ? Reference::DontCapture : Reference::CaptureAheadOfTime; + return Reference::fromQmlScopeObject(base, data->coreIndex(), data->notifyIndex(), capturePolicy); } } if (_contextObject) { - bool propertyExistsButForceNameLookup = false; - QQmlPropertyData *pd = lookupQmlCompliantProperty(_contextObject, name, &propertyExistsButForceNameLookup); - if (propertyExistsButForceNameLookup) - return 0; - if (pd) { - QV4::IR::Temp *base = _block->TEMP(_qmlContextTemp); - base->memberResolver = _function->New<QV4::IR::MemberExpressionResolver>(); - base->memberResolver->owner = _function; - initMetaObjectResolver(base->memberResolver, _contextObject); - return _block->MEMBER(base, _function->newString(name), pd, QV4::IR::Member::MemberOfQmlContextObject); + QQmlPropertyData *data = lookupQmlCompliantProperty(_contextObject, name); + if (data) { + // Q_INVOKABLEs can't be FINAL, so we have to look them up at run-time + if (data->isFunction()) + return Reference::fromName(this, name); + + Reference base = Reference::fromStackSlot(this, _qmlContextSlot); + Reference::PropertyCapturePolicy capturePolicy; + if (!data->isConstant() && !data->isQmlBinding()) + capturePolicy = Reference::CaptureAtRuntime; + else + capturePolicy = data->isConstant() ? Reference::DontCapture : Reference::CaptureAheadOfTime; + return Reference::fromQmlContextObject(base, data->coreIndex(), data->notifyIndex(), capturePolicy); } } - #else Q_UNUSED(name) #endif // V4_BOOTSTRAP - // fall back to name lookup at run-time. - return 0; + return Reference(); } #ifndef V4_BOOTSTRAP @@ -2055,7 +2322,7 @@ QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRev { if (notInRevision) *notInRevision = false; - QQmlPropertyData *d = cache->property(name, 0, 0); + QQmlPropertyData *d = cache->property(name, nullptr, nullptr); // Find the first property while (d && d->isFunction()) @@ -2063,7 +2330,7 @@ QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRev if (check != IgnoreRevision && d && !cache->isAllowedInRevision(d)) { if (notInRevision) *notInRevision = true; - return 0; + return nullptr; } else { return d; } @@ -2074,7 +2341,7 @@ QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevis { if (notInRevision) *notInRevision = false; - QQmlPropertyData *d = cache->property(name, 0, 0); + QQmlPropertyData *d = cache->property(name, nullptr, nullptr); if (notInRevision) *notInRevision = false; while (d && !(d->isFunction())) @@ -2082,7 +2349,7 @@ QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevis if (d && !cache->isAllowedInRevision(d)) { if (notInRevision) *notInRevision = true; - return 0; + return nullptr; } else if (d && d->isSignal()) { return d; } @@ -2095,7 +2362,7 @@ QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevis return cache->signal(d->notifyIndex()); } - return 0; + return nullptr; } IRLoader::IRLoader(const QV4::CompiledData::Unit *qmlData, QmlIR::Document *output) @@ -2107,12 +2374,12 @@ IRLoader::IRLoader(const QV4::CompiledData::Unit *qmlData, QmlIR::Document *outp void IRLoader::load() { - output->jsGenerator.stringTable.clear(); - for (uint i = 0; i < unit->stringTableSize; ++i) - output->jsGenerator.stringTable.registerString(unit->stringAt(i)); + output->jsGenerator.stringTable.initializeFromBackingUnit(unit); - for (quint32 i = 0; i < unit->nImports; ++i) - output->imports << unit->importAt(i); + const QV4::CompiledData::QmlUnit *qmlUnit = unit->qmlUnit(); + + for (quint32 i = 0; i < qmlUnit->nImports; ++i) + output->imports << qmlUnit->importAt(i); if (unit->flags & QV4::CompiledData::Unit::IsSingleton) { QmlIR::Pragma *p = New<QmlIR::Pragma>(); @@ -2121,10 +2388,8 @@ void IRLoader::load() output->pragmas << p; } - output->indexOfRootObject = unit->indexOfRootObject; - - for (uint i = 0; i < unit->nObjects; ++i) { - const QV4::CompiledData::Object *serializedObject = unit->objectAt(i); + for (uint i = 0; i < qmlUnit->nObjects; ++i) { + const QV4::CompiledData::Object *serializedObject = qmlUnit->objectAt(i); QmlIR::Object *object = loadObject(serializedObject); output->objects.append(object); } @@ -2206,6 +2471,22 @@ QmlIR::Object *IRLoader::loadObject(const QV4::CompiledData::Object *serializedO object->qmlSignals->append(s); } + for (uint i = 0; i < serializedObject->nEnums; ++i) { + const QV4::CompiledData::Enum *serializedEnum = serializedObject->enumAt(i); + QmlIR::Enum *e = pool->New<QmlIR::Enum>(); + e->nameIndex = serializedEnum->nameIndex; + e->location = serializedEnum->location; + e->enumValues = pool->New<QmlIR::PoolList<QmlIR::EnumValue> >(); + + for (uint i = 0; i < serializedEnum->nEnumValues; ++i) { + QmlIR::EnumValue *v = pool->New<QmlIR::EnumValue>(); + *static_cast<QV4::CompiledData::EnumValue*>(v) = *serializedEnum->enumValueAt(i); + e->enumValues->append(v); + } + + object->qmlEnums->append(e); + } + const QV4::CompiledData::Property *serializedProperty = serializedObject->propertyTable(); for (uint i = 0; i < serializedObject->nProperties; ++i, ++serializedProperty) { QmlIR::Property *p = pool->New<QmlIR::Property>(); @@ -2222,9 +2503,7 @@ QmlIR::Object *IRLoader::loadObject(const QV4::CompiledData::Object *serializedO } } - QQmlJS::Engine *jsParserEngine = &output->jsParserEngine; - - const QV4::CompiledData::LEUInt32 *functionIdx = serializedObject->functionOffsetTable(); + const quint32_le *functionIdx = serializedObject->functionOffsetTable(); for (uint i = 0; i < serializedObject->nFunctions; ++i, ++functionIdx) { QmlIR::Function *f = pool->New<QmlIR::Function>(); const QV4::CompiledData::Function *compiledFunction = unit->functionAt(*functionIdx); @@ -2234,26 +2513,8 @@ QmlIR::Object *IRLoader::loadObject(const QV4::CompiledData::Object *serializedO f->location = compiledFunction->location; f->nameIndex = compiledFunction->nameIndex; - QQmlJS::AST::FormalParameterList *paramList = 0; - const QV4::CompiledData::LEUInt32 *formalNameIdx = compiledFunction->formalsTable(); - for (uint i = 0; i < compiledFunction->nFormals; ++i, ++formalNameIdx) { - const QString formal = unit->stringAt(*formalNameIdx); - QStringRef paramNameRef = jsParserEngine->newStringRef(formal); - - if (paramList) - paramList = new (pool) QQmlJS::AST::FormalParameterList(paramList, paramNameRef); - else - paramList = new (pool) QQmlJS::AST::FormalParameterList(paramNameRef); - } - - if (paramList) - paramList = paramList->finish(); - - const QString name = unit->stringAt(compiledFunction->nameIndex); - f->functionDeclaration = new(pool) QQmlJS::AST::FunctionDeclaration(jsParserEngine->newStringRef(name), paramList, /*body*/0); - f->formals.allocate(pool, int(compiledFunction->nFormals)); - formalNameIdx = compiledFunction->formalsTable(); + const quint32_le *formalNameIdx = compiledFunction->formalsTable(); for (uint i = 0; i < compiledFunction->nFormals; ++i, ++formalNameIdx) f->formals[i] = *formalNameIdx; diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 64bf111d9a..8512b22fbd 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -57,14 +57,9 @@ #include <private/qqmljsmemorypool_p.h> #include <private/qv4codegen_p.h> #include <private/qv4compiler_p.h> -#include <private/qqmljslexer_p.h> #include <QTextStream> #include <QCoreApplication> -#ifndef V4_BOOTSTRAP -#include <private/qqmlpropertycache_p.h> -#endif - QT_BEGIN_NAMESPACE class QQmlPropertyCache; @@ -80,17 +75,16 @@ template <typename T> struct PoolList { PoolList() - : first(0) - , last(0) - , count(0) + : first(nullptr) + , last(nullptr) {} T *first; T *last; - int count; + int count = 0; int append(T *item) { - item->next = 0; + item->next = nullptr; if (last) last->next = item; else @@ -110,7 +104,7 @@ struct PoolList template <typename Sortable, typename Base, Sortable Base::*sortMember> T *findSortedInsertionPoint(T *item) const { - T *insertPos = 0; + T *insertPos = nullptr; for (T *it = first; it; it = it->next) { if (!(it->*sortMember <= item->*sortMember)) @@ -200,70 +194,26 @@ struct PoolList Iterator end() { return Iterator(nullptr); } }; -template <typename T> -class FixedPoolArray -{ - T *data; -public: - int count; - - 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))); - - if (QTypeInfo<T>::isComplex) { - for (int i = 0; i < count; ++i) - new (data + i) T(vector.at(i)); - } else { - memcpy(data, static_cast<const void*>(vector.constData()), count * sizeof(T)); - } - } - - 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]; - } +struct Object; - T &operator[](int index) { - Q_ASSERT(index >= 0 && index < count); - return data[index]; - } +struct EnumValue : public QV4::CompiledData::EnumValue +{ + EnumValue *next; +}; +struct Enum +{ + int nameIndex; + QV4::CompiledData::Location location; + PoolList<EnumValue> *enumValues; - int indexOf(const T &value) const { - for (int i = 0; i < count; ++i) - if (data[i] == value) - return i; - return -1; - } + int enumValueCount() const { return enumValues->count; } + PoolList<EnumValue>::Iterator enumValuesBegin() const { return enumValues->begin(); } + PoolList<EnumValue>::Iterator enumValuesEnd() const { return enumValues->end(); } - const T *begin() const { return data; } - const T *end() const { return data + count; } + Enum *next; }; -struct Object; struct SignalParameter : public QV4::CompiledData::Parameter { @@ -307,7 +257,6 @@ struct Alias : public QV4::CompiledData::Alias struct Function { - QQmlJS::AST::FunctionDeclaration *functionDeclaration; QV4::CompiledData::Location location; int nameIndex; quint32 index; // index in parsedQML::functions @@ -324,21 +273,13 @@ struct Function struct Q_QML_PRIVATE_EXPORT CompiledFunctionOrExpression { CompiledFunctionOrExpression() - : node(0) - , nameIndex(0) - , disableAcceleratedLookups(false) - , next(0) - {} - CompiledFunctionOrExpression(QQmlJS::AST::Node *n) - : node(n) - , nameIndex(0) - , disableAcceleratedLookups(false) - , next(0) {} - QQmlJS::AST::Node *node; // FunctionDeclaration, Statement or Expression - quint32 nameIndex; - bool disableAcceleratedLookups; - CompiledFunctionOrExpression *next; + + QQmlJS::AST::Node *parentNode = nullptr; // FunctionDeclaration, Statement or Expression + QQmlJS::AST::Node *node = nullptr; // FunctionDeclaration, Statement or Expression + quint32 nameIndex = 0; + bool disableAcceleratedLookups = false; + CompiledFunctionOrExpression *next = nullptr; }; struct Q_QML_PRIVATE_EXPORT Object @@ -359,6 +300,8 @@ public: int propertyCount() const { return properties->count; } Alias *firstAlias() const { return aliases->first; } int aliasCount() const { return aliases->count; } + const Enum *firstEnum() const { return qmlEnums->first; } + int enumCount() const { return qmlEnums->count; } const Signal *firstSignal() const { return qmlSignals->first; } int signalCount() const { return qmlSignals->count; } Binding *firstBinding() const { return bindings->first; } @@ -372,6 +315,8 @@ public: 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<Enum>::Iterator enumsBegin() const { return qmlEnums->begin(); } + PoolList<Enum>::Iterator enumsEnd() const { return qmlEnums->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(); } @@ -383,8 +328,7 @@ public: 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 appendEnum(Enum *enumeration); 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); @@ -400,7 +344,7 @@ public: FixedPoolArray<int> runtimeFunctionIndices; FixedPoolArray<quint32> namedObjectsInComponent; - int namedObjectsInComponentCount() const { return namedObjectsInComponent.count; } + int namedObjectsInComponentCount() const { return namedObjectsInComponent.size(); } const quint32 *namedObjectsInComponentTable() const { return namedObjectsInComponent.begin(); } private: @@ -408,6 +352,7 @@ private: PoolList<Property> *properties; PoolList<Alias> *aliases; + PoolList<Enum> *qmlEnums; PoolList<Signal> *qmlSignals; PoolList<Binding> *bindings; PoolList<Function> *functions; @@ -428,11 +373,10 @@ struct Q_QML_PRIVATE_EXPORT Document Document(bool debugMode); QString code; QQmlJS::Engine jsParserEngine; - QV4::IR::Module jsModule; + QV4::Compiler::Module jsModule; QList<const QV4::CompiledData::Import *> imports; QList<Pragma*> pragmas; QQmlJS::AST::UiProgram *program; - int indexOfRootObject; QVector<Object*> objects; QV4::Compiler::JSUnitGenerator jsGenerator; @@ -444,14 +388,14 @@ struct Q_QML_PRIVATE_EXPORT Document static void removeScriptPragmas(QString &script); }; -struct Q_QML_PRIVATE_EXPORT ScriptDirectivesCollector : public QQmlJS::Directives +class Q_QML_PRIVATE_EXPORT ScriptDirectivesCollector : public QQmlJS::Directives { - ScriptDirectivesCollector(QQmlJS::Engine *engine, QV4::Compiler::JSUnitGenerator *unitGenerator); - + QmlIR::Document *document; QQmlJS::Engine *engine; QV4::Compiler::JSUnitGenerator *jsGenerator; - QList<const QV4::CompiledData::Import *> imports; - bool hasPragmaLibrary; + +public: + ScriptDirectivesCollector(QmlIR::Document *doc); void pragmaLibrary() override; void importFile(const QString &jsfile, const QString &module, int lineNumber, int column) override; @@ -482,6 +426,7 @@ public: bool visit(QQmlJS::AST::UiArrayBinding *ast) override; bool visit(QQmlJS::AST::UiObjectBinding *ast) override; bool visit(QQmlJS::AST::UiObjectDefinition *ast) override; + bool visit(QQmlJS::AST::UiEnumDeclaration *ast) override; bool visit(QQmlJS::AST::UiPublicMember *ast) override; bool visit(QQmlJS::AST::UiScriptBinding *ast) override; bool visit(QQmlJS::AST::UiSourceElement *ast) override; @@ -489,8 +434,8 @@ public: void accept(QQmlJS::AST::Node *node); // returns index in _objects - bool defineQMLObject(int *objectIndex, QQmlJS::AST::UiQualifiedId *qualifiedTypeNameId, const QQmlJS::AST::SourceLocation &location, QQmlJS::AST::UiObjectInitializer *initializer, Object *declarationsOverride = 0); - bool defineQMLObject(int *objectIndex, QQmlJS::AST::UiObjectDefinition *node, Object *declarationsOverride = 0) + bool defineQMLObject(int *objectIndex, QQmlJS::AST::UiQualifiedId *qualifiedTypeNameId, const QQmlJS::AST::SourceLocation &location, QQmlJS::AST::UiObjectInitializer *initializer, Object *declarationsOverride = nullptr); + bool defineQMLObject(int *objectIndex, QQmlJS::AST::UiObjectDefinition *node, Object *declarationsOverride = nullptr) { return defineQMLObject(objectIndex, node->qualifiedTypeNameId, node->qualifiedTypeNameId->firstSourceLocation(), node->initializer, declarationsOverride); } static QString asString(QQmlJS::AST::UiQualifiedId *node); @@ -501,11 +446,12 @@ public: QStringRef textRefAt(const QQmlJS::AST::SourceLocation &first, const QQmlJS::AST::SourceLocation &last) const; - void setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST::Statement *statement); + void setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST::Statement *statement, AST::Node *parentNode); + void tryGeneratingTranslationBinding(const QStringRef &base, QQmlJS::AST::ArgumentList *args, QV4::CompiledData::Binding *binding); - void appendBinding(QQmlJS::AST::UiQualifiedId *name, QQmlJS::AST::Statement *value); + void appendBinding(QQmlJS::AST::UiQualifiedId *name, QQmlJS::AST::Statement *value, AST::Node *parentNode); void appendBinding(QQmlJS::AST::UiQualifiedId *name, int objectIndex, bool isOnAssignment = false); - 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, QQmlJS::AST::Statement *value, AST::Node *parentNode); 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); @@ -528,6 +474,8 @@ public: static bool isStatementNodeScript(QQmlJS::AST::Statement *statement); static bool isRedundantNullInitializerForPropertyDeclaration(Property *property, QQmlJS::AST::Statement *statement); + QString sanityCheckFunctionNames(Object *obj, const QSet<QString> &illegalNames, QQmlJS::AST::SourceLocation *errorLocation); + QList<QQmlJS::DiagnosticMessage> errors; QSet<QString> illegalNames; @@ -548,7 +496,7 @@ public: struct Q_QML_PRIVATE_EXPORT QmlUnitGenerator { - QV4::CompiledData::Unit *generate(Document &output, const QV4::CompiledData::DependentTypesHasher &dependencyHasher = QV4::CompiledData::DependentTypesHasher()); + void generate(Document &output, const QV4::CompiledData::DependentTypesHasher &dependencyHasher = QV4::CompiledData::DependentTypesHasher()); private: typedef bool (Binding::*BindingFilter)() const; @@ -558,7 +506,7 @@ private: #ifndef V4_BOOTSTRAP struct Q_QML_EXPORT PropertyResolver { - PropertyResolver(const QQmlPropertyCache *cache) + PropertyResolver(const QQmlRefPointer<QQmlPropertyCache> &cache) : cache(cache) {} @@ -572,20 +520,20 @@ struct Q_QML_EXPORT PropertyResolver IgnoreRevision }; - QQmlPropertyData *property(const QString &name, bool *notInRevision = 0, RevisionCheck check = CheckRevision) const; + QQmlPropertyData *property(const QString &name, bool *notInRevision = nullptr, RevisionCheck check = CheckRevision) const; // This code must match the semantics of QQmlPropertyPrivate::findSignalByName QQmlPropertyData *signal(const QString &name, bool *notInRevision) const; - const QQmlPropertyCache *cache; + QQmlRefPointer<QQmlPropertyCache> cache; }; #endif -struct Q_QML_PRIVATE_EXPORT JSCodeGen : public QQmlJS::Codegen +struct Q_QML_PRIVATE_EXPORT JSCodeGen : public QV4::Compiler::Codegen { - JSCodeGen(const QString &fileName, const QString &sourceCode, QV4::IR::Module *jsModule, - QQmlJS::Engine *jsEngine, QQmlJS::AST::UiProgram *qmlRoot, QQmlTypeNameCache *imports, - const QV4::Compiler::StringTableGenerator *stringPool); + JSCodeGen(const QString &sourceCode, QV4::Compiler::JSUnitGenerator *jsUnitGenerator, QV4::Compiler::Module *jsModule, + QQmlJS::Engine *jsEngine, QQmlJS::AST::UiProgram *qmlRoot, + QQmlTypeNameCache *imports, const QV4::Compiler::StringTableGenerator *stringPool, const QSet<QString> &globalNames); struct IdMapping { @@ -601,12 +549,18 @@ struct Q_QML_PRIVATE_EXPORT JSCodeGen : public QQmlJS::Codegen // Returns mapping from input functions to index in IR::Module::functions / compiledData->runtimeFunctions QVector<int> generateJSCodeForFunctionsAndBindings(const QList<CompiledFunctionOrExpression> &functions); + int defineFunction(const QString &name, AST::Node *ast, + AST::FormalParameterList *formals, + AST::StatementList *body) override; + protected: void beginFunctionBodyHook() override; - QV4::IR::Expr *fallbackNameLookup(const QString &name, int line, int col) override; + bool canAccelerateGlobalLookups() const override { return !_disableAcceleratedLookups; } + Reference fallbackNameLookup(const QString &name) override; private: - QQmlPropertyData *lookupQmlCompliantProperty(QQmlPropertyCache *cache, const QString &name, bool *propertyExistsButForceNameLookup = 0); + // returns nullptr if lookup needs to happen by name + QQmlPropertyData *lookupQmlCompliantProperty(QQmlPropertyCache *cache, const QString &name); QString sourceCode; QQmlJS::Engine *jsEngine; // needed for memory pool @@ -618,8 +572,8 @@ private: ObjectIdMapping _idObjects; QQmlPropertyCache *_contextObject; QQmlPropertyCache *_scopeObject; - int _qmlContextTemp; - int _importedScriptsTemp; + int _qmlContextSlot; + int _importedScriptsSlot; }; struct Q_QML_PRIVATE_EXPORT IRLoader { @@ -639,6 +593,17 @@ private: } // namespace QmlIR +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(); } +}; + QT_END_NAMESPACE #endif // QQMLIRBUILDER_P_H diff --git a/src/qml/compiler/qqmlpropertycachecreator.cpp b/src/qml/compiler/qqmlpropertycachecreator.cpp index f8d63ec634..fb54da5b73 100644 --- a/src/qml/compiler/qqmlpropertycachecreator.cpp +++ b/src/qml/compiler/qqmlpropertycachecreator.cpp @@ -45,26 +45,54 @@ 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, QQmlPropertyCache *referencingObjectPropertyCache) + : referencingObjectIndex(referencingObjectIndex) + , instantiatingBinding(instantiatingBinding) + , instantiatingPropertyName(instantiatingPropertyName) + , referencingObjectPropertyCache(referencingObjectPropertyCache) { +} +bool QQmlBindingInstantiationContext::resolveInstantiatingProperty() +{ + if (!instantiatingBinding || instantiatingBinding->type != QV4::CompiledData::Binding::Type_GroupProperty) + return true; + + Q_ASSERT(referencingObjectIndex >= 0); + Q_ASSERT(referencingObjectPropertyCache); + Q_ASSERT(instantiatingBinding->propertyNameIndex != 0); + + bool notInRevision = false; + instantiatingProperty = QmlIR::PropertyResolver(referencingObjectPropertyCache).property(instantiatingPropertyName, ¬InRevision, QmlIR::PropertyResolver::IgnoreRevision); + return instantiatingProperty != nullptr; } -QQmlBindingInstantiationContext::QQmlBindingInstantiationContext(int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding, const QString &instantiatingPropertyName, const QQmlPropertyCache *referencingObjectPropertyCache) - : referencingObjectIndex(referencingObjectIndex) - , instantiatingBinding(instantiatingBinding) - , instantiatingProperty(nullptr) +QQmlRefPointer<QQmlPropertyCache> QQmlBindingInstantiationContext::instantiatingPropertyCache(QQmlEnginePrivate *enginePrivate) const { - if (instantiatingBinding && instantiatingBinding->type == QV4::CompiledData::Binding::Type_GroupProperty) { - Q_ASSERT(referencingObjectIndex >= 0); - Q_ASSERT(referencingObjectPropertyCache); - Q_ASSERT(instantiatingBinding->propertyNameIndex != 0); + if (instantiatingProperty) { + if (instantiatingProperty->isQObject()) { + return enginePrivate->rawPropertyCacheForType(instantiatingProperty->propType(), instantiatingProperty->typeMinorVersion()); + } else if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(instantiatingProperty->propType())) { + return enginePrivate->cache(vtmo, instantiatingProperty->typeMinorVersion()); + } + } + return QQmlRefPointer<QQmlPropertyCache>(); +} + +void QQmlPendingGroupPropertyBindings::resolveMissingPropertyCaches(QQmlEnginePrivate *enginePrivate, QQmlPropertyCacheVector *propertyCaches) const +{ + for (QQmlBindingInstantiationContext pendingBinding: *this) { + const int groupPropertyObjectIndex = pendingBinding.instantiatingBinding->value.objectIndex; + + if (propertyCaches->at(groupPropertyObjectIndex)) + continue; + + if (!pendingBinding.resolveInstantiatingProperty()) + continue; - bool notInRevision = false; - instantiatingProperty = QmlIR::PropertyResolver(referencingObjectPropertyCache).property(instantiatingPropertyName, ¬InRevision); + auto cache = pendingBinding.instantiatingPropertyCache(enginePrivate); + propertyCaches->set(groupPropertyObjectIndex, cache); } } diff --git a/src/qml/compiler/qqmlpropertycachecreator_p.h b/src/qml/compiler/qqmlpropertycachecreator_p.h index 3c14abc019..7d416561bb 100644 --- a/src/qml/compiler/qqmlpropertycachecreator_p.h +++ b/src/qml/compiler/qqmlpropertycachecreator_p.h @@ -50,18 +50,32 @@ // We mean it. // -#include "qqmltypecompiler_p.h" #include <private/qqmlvaluetype_p.h> #include <private/qqmlengine_p.h> +#include <private/qqmlmetaobject_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; + QQmlBindingInstantiationContext() {} + QQmlBindingInstantiationContext(int referencingObjectIndex, + const QV4::CompiledData::Binding *instantiatingBinding, + const QString &instantiatingPropertyName, + QQmlPropertyCache *referencingObjectPropertyCache); + + bool resolveInstantiatingProperty(); + QQmlRefPointer<QQmlPropertyCache> instantiatingPropertyCache(QQmlEnginePrivate *enginePrivate) const; + + int referencingObjectIndex = -1; + const QV4::CompiledData::Binding *instantiatingBinding = nullptr; + QString instantiatingPropertyName; + QQmlRefPointer<QQmlPropertyCache> referencingObjectPropertyCache; + QQmlPropertyData *instantiatingProperty = nullptr; +}; + +struct QQmlPendingGroupPropertyBindings : public QVector<QQmlBindingInstantiationContext> +{ + void resolveMissingPropertyCaches(QQmlEnginePrivate *enginePrivate, QQmlPropertyCacheVector *propertyCaches) const; }; struct QQmlPropertyCacheCreatorBase @@ -77,14 +91,17 @@ class QQmlPropertyCacheCreator : public QQmlPropertyCacheCreatorBase public: typedef typename ObjectContainer::CompiledObject CompiledObject; - QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, QQmlEnginePrivate *enginePrivate, const ObjectContainer *objectContainer, const QQmlImports *imports); + QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, + QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings, + 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); + QQmlRefPointer<QQmlPropertyCache> propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlCompileError *error) const; + QQmlCompileError createMetaObject(int objectIndex, const CompiledObject *obj, const QQmlRefPointer<QQmlPropertyCache> &baseTypeCache); QString stringAt(int index) const { return objectContainer->stringAt(index); } @@ -92,14 +109,19 @@ protected: const ObjectContainer * const objectContainer; const QQmlImports * const imports; QQmlPropertyCacheVector *propertyCaches; + QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings; }; template <typename ObjectContainer> -inline QQmlPropertyCacheCreator<ObjectContainer>::QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, QQmlEnginePrivate *enginePrivate, const ObjectContainer *objectContainer, const QQmlImports *imports) +inline QQmlPropertyCacheCreator<ObjectContainer>::QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, + QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings, + QQmlEnginePrivate *enginePrivate, + const ObjectContainer *objectContainer, const QQmlImports *imports) : enginePrivate(enginePrivate) , objectContainer(objectContainer) , imports(imports) , propertyCaches(propertyCaches) + , pendingGroupPropertyBindings(pendingGroupPropertyBindings) { propertyCaches->resize(objectContainer->objectCount()); } @@ -108,7 +130,7 @@ template <typename ObjectContainer> inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjects() { QQmlBindingInstantiationContext context; - return buildMetaObjectRecursively(objectContainer->rootObjectIndex(), context); + return buildMetaObjectRecursively(/*root object*/0, context); } template <typename ObjectContainer> @@ -116,7 +138,7 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObje { const CompiledObject *obj = objectContainer->objectAt(objectIndex); - bool needVMEMetaObject = obj->propertyCount() != 0 || obj->aliasCount() != 0 || obj->signalCount() != 0 || obj->functionCount() != 0; + bool needVMEMetaObject = obj->propertyCount() != 0 || obj->aliasCount() != 0 || obj->signalCount() != 0 || obj->functionCount() != 0 || obj->enumCount() != 0; if (!needVMEMetaObject) { auto binding = obj->bindingsBegin(); auto end = obj->bindingsEnd(); @@ -129,9 +151,9 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObje 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); + auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex); Q_ASSERT(typeRef); - QQmlPropertyCache *baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + QQmlRefPointer<QQmlPropertyCache> baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); QQmlCompileError error = createMetaObject(context.referencingObjectIndex, obj, baseTypeCache); if (error.isSet()) return error; @@ -145,7 +167,7 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObje } } - QQmlPropertyCache *baseTypeCache; + QQmlRefPointer<QQmlPropertyCache> baseTypeCache; { QQmlCompileError error; baseTypeCache = propertyCacheForObject(obj, context, &error); @@ -169,6 +191,14 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObje for ( ; binding != end; ++binding) if (binding->type >= QV4::CompiledData::Binding::Type_Object) { QQmlBindingInstantiationContext context(objectIndex, &(*binding), stringAt(binding->propertyNameIndex), thisCache); + + // Binding to group property where we failed to look up the type of the + // property? Possibly a group property that is an alias that's not resolved yet. + // Let's attempt to resolve it after we're done with the aliases and fill in the + // propertyCaches entry then. + if (!context.resolveInstantiatingProperty()) + pendingGroupPropertyBindings->append(context); + QQmlCompileError error = buildMetaObjectRecursively(binding->value.objectIndex, context); if (error.isSet()) return error; @@ -180,16 +210,12 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObje } template <typename ObjectContainer> -inline QQmlPropertyCache *QQmlPropertyCacheCreator<ObjectContainer>::propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlCompileError *error) const +inline QQmlRefPointer<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); - } + return context.instantiatingPropertyCache(enginePrivate); } else if (obj->inheritedTypeNameIndex != 0) { - auto *typeRef = objectContainer->resolvedTypes.value(obj->inheritedTypeNameIndex); + auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex); Q_ASSERT(typeRef); if (typeRef->isFullyDynamicType) { @@ -209,26 +235,25 @@ inline QQmlPropertyCache *QQmlPropertyCacheCreator<ObjectContainer>::propertyCac return typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); } else if (context.instantiatingBinding && context.instantiatingBinding->isAttachedProperty()) { - auto *typeRef = objectContainer->resolvedTypes.value(context.instantiatingBinding->propertyNameIndex); + auto *typeRef = objectContainer->resolvedType( + context.instantiatingBinding->propertyNameIndex); Q_ASSERT(typeRef); - QQmlType *qmltype = typeRef->type; - if (!qmltype) { + QQmlType qmltype = typeRef->type; + if (!qmltype.isValid()) { QString propertyName = stringAt(context.instantiatingBinding->propertyNameIndex); - if (imports->resolveType(propertyName, &qmltype, 0, 0, 0)) { - if (qmltype->isComposite()) { - QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); + if (imports->resolveType(propertyName, &qmltype, nullptr, nullptr, nullptr)) { + if (qmltype.isComposite()) { + QQmlRefPointer<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; + const QMetaObject *attachedMo = qmltype.attachedPropertiesType(enginePrivate); if (!attachedMo) { *error = QQmlCompileError(context.instantiatingBinding->location, QQmlPropertyCacheCreatorBase::tr("Non-existent attached object")); return nullptr; @@ -239,12 +264,12 @@ inline QQmlPropertyCache *QQmlPropertyCacheCreator<ObjectContainer>::propertyCac } template <typename ObjectContainer> -inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int objectIndex, const CompiledObject *obj, QQmlPropertyCache *baseTypeCache) +inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int objectIndex, const CompiledObject *obj, const QQmlRefPointer<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())); + obj->signalCount() + obj->propertyCount() + obj->aliasCount(), obj->enumCount())); propertyCaches->set(objectIndex, cache); propertyCaches->setNeedsVMEMetaObject(objectIndex); @@ -278,7 +303,7 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj QByteArray newClassName; - if (objectIndex == objectContainer->rootObjectIndex()) { + if (objectIndex == /*root object*/0) { const QString path = objectContainer->url().path(); int lastSlash = path.lastIndexOf(QLatin1Char('/')); if (lastSlash > -1) { @@ -289,7 +314,7 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj } } if (newClassName.isEmpty()) { - newClassName = QQmlMetaObject(baseTypeCache).className(); + newClassName = QQmlMetaObject(baseTypeCache.data()).className(); newClassName.append("_QML_"); newClassName.append(QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1))); } @@ -329,7 +354,7 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj // 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; + QQmlPropertyCache *parentCache = cache.data(); while ((parentCache = parentCache->parent())) { if (int pSigCount = parentCache->signalCount()) { int pSigOffset = parentCache->signalOffset(); @@ -370,6 +395,21 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); } + auto e = obj->enumsBegin(); + auto eend = obj->enumsEnd(); + for ( ; e != eend; ++e) { + const int enumValueCount = e->enumValueCount(); + QVector<QQmlEnumValue> values; + values.reserve(enumValueCount); + + auto enumValue = e->enumValuesBegin(); + auto end = e->enumValuesEnd(); + for ( ; enumValue != end; ++enumValue) + values.append(QQmlEnumValue(stringAt(enumValue->nameIndex), enumValue->value)); + + cache->appendEnum(stringAt(e->nameIndex), values); + } + // Dynamic signals auto s = obj->signalsBegin(); auto send = obj->signalsEnd(); @@ -395,22 +435,20 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj // 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)) + QQmlType qmltype; + if (!imports->resolveType(customTypeName, &qmltype, nullptr, nullptr, nullptr)) return QQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr("Invalid signal parameter type: %1").arg(customTypeName)); - if (qmltype->isComposite()) { - QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); + if (qmltype.isComposite()) { + QQmlRefPointer<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(); + paramTypes[i + 1] = qmltype.typeId(); } } } @@ -426,7 +464,7 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj seenSignals.insert(signalName); cache->appendSignal(signalName, flags, effectiveMethodIndex++, - paramCount?paramTypes.constData():0, names); + paramCount?paramTypes.constData():nullptr, names); } @@ -461,6 +499,7 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj pend = obj->propertiesEnd(); for ( ; p != pend; ++p, ++propertyIdx) { int propertyType = 0; + int propertTypeMinorVersion = 0; QQmlPropertyData::Flags propertyFlags; if (p->type == QV4::CompiledData::Property::Var) { @@ -475,14 +514,14 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj 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)) { + QQmlType qmltype; + if (!imports->resolveType(stringAt(p->customTypeNameIndex), &qmltype, nullptr, nullptr, nullptr)) { return QQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr("Invalid property type")); } - Q_ASSERT(qmltype); - if (qmltype->isComposite()) { - QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); + Q_ASSERT(qmltype.isValid()); + if (qmltype.isComposite()) { + QQmlRefPointer<QQmlTypeData> tdata = enginePrivate->typeLoader.getType(qmltype.sourceUrl()); Q_ASSERT(tdata); Q_ASSERT(tdata->isComplete()); @@ -493,13 +532,12 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj } else { propertyType = compilationUnit->listMetaTypeId; } - - tdata->release(); } else { if (p->type == QV4::CompiledData::Property::Custom) { - propertyType = qmltype->typeId(); + propertyType = qmltype.typeId(); + propertTypeMinorVersion = qmltype.minorVersion(); } else { - propertyType = qmltype->qListTypeId(); + propertyType = qmltype.qListTypeId(); } } @@ -517,7 +555,7 @@ inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObj if (!obj->defaultPropertyIsAlias && propertyIdx == obj->indexOfDefaultPropertyOrAlias) cache->_defaultPropertyName = propertyName; cache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, - propertyType, effectiveSignalIndex); + propertyType, propertTypeMinorVersion, effectiveSignalIndex); effectiveSignalIndex++; } @@ -536,11 +574,11 @@ public: void appendAliasPropertiesToMetaObjects(); - void appendAliasesToPropertyCache(const CompiledObject &component, int objectIndex); + QQmlCompileError 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); + QQmlCompileError propertyDataForAlias(const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, int *rev, QQmlPropertyRawData::Flags *propertyFlags); void collectObjectsWithAliasesRecursively(int objectIndex, QVector<int> *objectsWithAliases) const; @@ -561,7 +599,9 @@ inline QQmlPropertyCacheAliasCreator<ObjectContainer>::QQmlPropertyCacheAliasCre template <typename ObjectContainer> inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertiesToMetaObjects() { - for (int i = 0; i < objectContainer->objectCount(); ++i) { + // skip the root object (index 0) as that one does not have a first object index originating + // from a binding. + for (int i = 1; i < objectContainer->objectCount(); ++i) { const CompiledObject &component = *objectContainer->objectAt(i); if (!(component.flags & QV4::CompiledData::Object::IsComponent)) continue; @@ -570,7 +610,7 @@ inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertie appendAliasPropertiesInMetaObjectsWithinComponent(component, rootBinding->value.objectIndex); } - const int rootObjectIndex = objectContainer->rootObjectIndex(); + const int rootObjectIndex = 0; appendAliasPropertiesInMetaObjectsWithinComponent(*objectContainer->objectAt(rootObjectIndex), rootObjectIndex); } @@ -633,7 +673,7 @@ inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::collectObjectsWithAl objectsWithAliases->append(objectIndex); // Stop at Component boundary - if (object.flags & QV4::CompiledData::Object::IsComponent && objectIndex != objectContainer->rootObjectIndex()) + if (object.flags & QV4::CompiledData::Object::IsComponent && objectIndex != /*root object*/0) return; auto binding = object.bindingsBegin(); @@ -649,8 +689,8 @@ inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::collectObjectsWithAl } template <typename ObjectContainer> -inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataForAlias( - const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, +inline QQmlCompileError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataForAlias( + const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, int *minorVersion, QQmlPropertyData::Flags *propertyFlags) { const int targetObjectIndex = objectForId(component, alias.targetObjectId); @@ -668,18 +708,24 @@ inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataForAlias auto targetAlias = targetObject.aliasesBegin(); for (uint i = 0; i < alias.localAliasIndex; ++i) ++targetAlias; - propertyDataForAlias(component, *targetAlias, type, propertyFlags); - return; + return propertyDataForAlias(component, *targetAlias, type, minorVersion, propertyFlags); } else if (alias.encodedMetaPropertyIndex == -1) { Q_ASSERT(alias.flags & QV4::CompiledData::Alias::AliasPointsToPointerObject); - auto *typeRef = objectContainer->resolvedTypes.value(targetObject.inheritedTypeNameIndex); - Q_ASSERT(typeRef); + auto *typeRef = objectContainer->resolvedType(targetObject.inheritedTypeNameIndex); + if (!typeRef) { + // Can be caused by the alias target not being a valid id or property. E.g.: + // property alias dataValue: dataVal + // invalidAliasComponent { id: dataVal } + return QQmlCompileError(targetObject.location, QQmlPropertyCacheCreatorBase::tr("Invalid alias target")); + } - if (typeRef->type) - *type = typeRef->type->typeId(); + if (typeRef->type.isValid()) + *type = typeRef->type.typeId(); else *type = typeRef->compilationUnit->metaTypeId; + *minorVersion = typeRef->minorVersion; + propertyFlags->type = QQmlPropertyData::Flags::QObjectDerivedType; } else { int coreIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).coreIndex(); @@ -716,15 +762,16 @@ inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataForAlias propertyFlags->isWritable = !(alias.flags & QV4::CompiledData::Property::IsReadOnly) && writable; propertyFlags->isResettable = resettable; + return QQmlCompileError(); } template <typename ObjectContainer> -inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasesToPropertyCache( +inline QQmlCompileError QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasesToPropertyCache( const CompiledObject &component, int objectIndex) { const CompiledObject &object = *objectContainer->objectAt(objectIndex); if (!object.aliasCount()) - return; + return QQmlCompileError(); QQmlPropertyCache *propertyCache = propertyCaches->at(objectIndex); Q_ASSERT(propertyCache); @@ -739,8 +786,11 @@ inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasesToPrope Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); int type = 0; + int minorVersion = 0; QQmlPropertyData::Flags propertyFlags; - propertyDataForAlias(component, *alias, &type, &propertyFlags); + QQmlCompileError error = propertyDataForAlias(component, *alias, &type, &minorVersion, &propertyFlags); + if (error.isSet()) + return error; const QString propertyName = objectContainer->stringAt(alias->nameIndex); @@ -748,8 +798,10 @@ inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasesToPrope propertyCache->_defaultPropertyName = propertyName; propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, - type, effectiveSignalIndex++); + type, minorVersion, effectiveSignalIndex++); } + + return QQmlCompileError(); } template <typename ObjectContainer> diff --git a/src/qml/compiler/qqmlpropertyvalidator.cpp b/src/qml/compiler/qqmlpropertyvalidator.cpp index 383c20239f..4714f505a7 100644 --- a/src/qml/compiler/qqmlpropertyvalidator.cpp +++ b/src/qml/compiler/qqmlpropertyvalidator.cpp @@ -45,20 +45,20 @@ QT_BEGIN_NAMESPACE -QQmlPropertyValidator::QQmlPropertyValidator(QQmlEnginePrivate *enginePrivate, const QQmlImports &imports, QV4::CompiledData::CompilationUnit *compilationUnit) +QQmlPropertyValidator::QQmlPropertyValidator(QQmlEnginePrivate *enginePrivate, const QQmlImports &imports, const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit) : enginePrivate(enginePrivate) + , compilationUnit(compilationUnit) , imports(imports) - , qmlUnit(compilationUnit->data) - , resolvedTypes(compilationUnit->resolvedTypes) + , qmlUnit(compilationUnit->unitData()) , propertyCaches(compilationUnit->propertyCaches) , bindingPropertyDataPerObject(&compilationUnit->bindingPropertyDataPerObject) { - bindingPropertyDataPerObject->resize(qmlUnit->nObjects); + bindingPropertyDataPerObject->resize(compilationUnit->objectCount()); } QVector<QQmlCompileError> QQmlPropertyValidator::validate() { - return validateObject(qmlUnit->indexOfRootObject, /*instantiatingBinding*/0); + return validateObject(/*root object*/0, /*instantiatingBinding*/nullptr); } typedef QVarLengthArray<const QV4::CompiledData::Binding *, 8> GroupPropertyVector; @@ -81,7 +81,7 @@ struct BindingFinder QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding, bool populatingValueTypeGroupProperty) const { - const QV4::CompiledData::Object *obj = qmlUnit->objectAt(objectIndex); + const QV4::CompiledData::Object *obj = compilationUnit->objectAt(objectIndex); if (obj->flags & QV4::CompiledData::Object::IsComponent) { Q_ASSERT(obj->nBindings == 1); @@ -94,20 +94,10 @@ QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int 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(); + QQmlCustomParser *customParser = nullptr; + if (auto typeRef = resolvedType(obj->inheritedTypeNameIndex)) { + if (typeRef->type.isValid()) + customParser = typeRef->type.customParser(); } QList<const QV4::CompiledData::Binding*> customBindings; @@ -134,7 +124,7 @@ QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int objectIndex, QmlIR::PropertyResolver propertyResolver(propertyCache); QString defaultPropertyName; - QQmlPropertyData *defaultProperty = 0; + QQmlPropertyData *defaultProperty = nullptr; if (obj->indexOfDefaultPropertyOrAlias != -1) { QQmlPropertyCache *cache = propertyCache->parent(); defaultPropertyName = cache->defaultPropertyName(); @@ -167,19 +157,21 @@ QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int objectIndex, bool isGroupProperty = instantiatingBinding && instantiatingBinding->type == QV4::CompiledData::Binding::Type_GroupProperty; bool notInRevision = false; - QQmlPropertyData *pd = 0; + QQmlPropertyData *pd = nullptr; if (!name.isEmpty()) { if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression - || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) { pd = propertyResolver.signal(name, ¬InRevision); - else - pd = propertyResolver.property(name, ¬InRevision, isGroupProperty ? QmlIR::PropertyResolver::IgnoreRevision : QmlIR::PropertyResolver::CheckRevision); + } else { + pd = propertyResolver.property(name, ¬InRevision, + 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)); + auto *objectType = resolvedType(obj->inheritedTypeNameIndex); + if (objectType && objectType->type.isValid()) { + 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)); } @@ -197,16 +189,22 @@ QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int objectIndex, collectedBindingPropertyData[i] = pd; if (name.constData()->isUpper() && !binding->isAttachedProperty()) { - QQmlType *type = 0; - QQmlImportNamespace *typeNamespace = 0; - imports.resolveType(stringAt(binding->propertyNameIndex), &type, 0, 0, &typeNamespace); + QQmlType type; + QQmlImportNamespace *typeNamespace = nullptr; + imports.resolveType(stringAt(binding->propertyNameIndex), &type, nullptr, nullptr, &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())); + const bool populatingValueTypeGroupProperty + = pd + && QQmlValueTypeFactory::metaObjectForMetaType(pd->propType()) + && !(binding->flags & QV4::CompiledData::Binding::IsOnAssignment); + const QVector<QQmlCompileError> subObjectValidatorErrors + = validateObject(binding->value.objectIndex, binding, + populatingValueTypeGroupProperty); if (!subObjectValidatorErrors.isEmpty()) return subObjectValidatorErrors; } @@ -279,7 +277,11 @@ QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int objectIndex, } } else { if (!enginePrivate->propertyCacheForType(pd->propType())) { - return recordError(binding->location, tr("Invalid grouped property access")); + return recordError(binding->location, + tr("Invalid grouped property access: Property \"%1\" with type \"%2\", which is not a value type") + .arg(name) + .arg(QString::fromLatin1(QMetaType::typeName(pd->propType()))) + ); } } } @@ -297,6 +299,9 @@ QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int objectIndex, } if (obj->idNameIndex) { + if (populatingValueTypeGroupProperty) + return recordError(obj->locationOfIdProperty, tr("Invalid use of id property with a value type")); + bool notInRevision = false; collectedBindingPropertyData << propertyResolver.property(QStringLiteral("id"), ¬InRevision); } @@ -306,10 +311,10 @@ QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int objectIndex, customParser->validator = this; customParser->engine = enginePrivate; customParser->imports = &imports; - customParser->verifyBindings(qmlUnit, customBindings); - customParser->validator = 0; - customParser->engine = 0; - customParser->imports = (QQmlImports*)0; + customParser->verifyBindings(compilationUnit, customBindings); + customParser->validator = nullptr; + customParser->engine = nullptr; + customParser->imports = (QQmlImports*)nullptr; QVector<QQmlCompileError> parserErrors = customParser->errors(); if (!parserErrors.isEmpty()) return parserErrors; @@ -333,7 +338,7 @@ QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache if (binding->flags & QV4::CompiledData::Binding::IsResolvedEnum) return noError; - QString value = binding->valueAsString(qmlUnit); + QString value = binding->valueAsString(compilationUnit.data()); QMetaProperty p = propertyCache->firstCppMetaObject()->property(property->coreIndex()); bool ok; if (p.isFlagType()) { @@ -347,148 +352,163 @@ QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache return noError; } + auto warnOrError = [&](const QString &error) { + if (binding->type == QV4::CompiledData::Binding::Type_Null) { + QQmlError warning; + warning.setUrl(compilationUnit->url()); + warning.setLine(binding->valueLocation.line); + warning.setColumn(binding->valueLocation.column); + warning.setDescription(error + tr(" - Assigning null to incompatible properties in QML " + "is deprecated. This will become a compile error in " + "future versions of Qt.")); + enginePrivate->warning(warning); + return noError; + } + return QQmlCompileError(binding->valueLocation, error); + }; + switch (property->propType()) { case QMetaType::QVariant: break; case QVariant::String: { if (!binding->evaluatesToString()) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: string expected")); + return warnOrError(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")); + return warnOrError(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")); + return warnOrError(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")); + return warnOrError(tr("Invalid property assignment: url expected")); } } break; case QVariant::UInt: { if (binding->type == QV4::CompiledData::Binding::Type_Number) { - double d = binding->valueAsNumber(); + double d = binding->valueAsNumber(compilationUnit->constants); if (double(uint(d)) == d) return noError; } - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: unsigned int expected")); + return warnOrError(tr("Invalid property assignment: unsigned int expected")); } break; case QVariant::Int: { if (binding->type == QV4::CompiledData::Binding::Type_Number) { - double d = binding->valueAsNumber(); + double d = binding->valueAsNumber(compilationUnit->constants); if (double(int(d)) == d) return noError; } - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: int expected")); + return warnOrError(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")); + return warnOrError(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")); + return warnOrError(tr("Invalid property assignment: number expected")); } } break; case QVariant::Color: { bool ok = false; - QQmlStringConverters::rgbaFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::rgbaFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: color expected")); + return warnOrError(tr("Invalid property assignment: color expected")); } } break; #if QT_CONFIG(datestring) case QVariant::Date: { bool ok = false; - QQmlStringConverters::dateFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::dateFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: date expected")); + return warnOrError(tr("Invalid property assignment: date expected")); } } break; case QVariant::Time: { bool ok = false; - QQmlStringConverters::timeFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::timeFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: time expected")); + return warnOrError(tr("Invalid property assignment: time expected")); } } break; case QVariant::DateTime: { bool ok = false; - QQmlStringConverters::dateTimeFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::dateTimeFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: datetime expected")); + return warnOrError(tr("Invalid property assignment: datetime expected")); } } break; #endif // datestring case QVariant::Point: { bool ok = false; - QQmlStringConverters::pointFFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::pointFFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: point expected")); + return warnOrError(tr("Invalid property assignment: point expected")); } } break; case QVariant::PointF: { bool ok = false; - QQmlStringConverters::pointFFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::pointFFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: point expected")); + return warnOrError(tr("Invalid property assignment: point expected")); } } break; case QVariant::Size: { bool ok = false; - QQmlStringConverters::sizeFFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::sizeFFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: size expected")); + return warnOrError(tr("Invalid property assignment: size expected")); } } break; case QVariant::SizeF: { bool ok = false; - QQmlStringConverters::sizeFFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::sizeFFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: size expected")); + return warnOrError(tr("Invalid property assignment: size expected")); } } break; case QVariant::Rect: { bool ok = false; - QQmlStringConverters::rectFFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::rectFFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: rect expected")); + return warnOrError(tr("Invalid property assignment: rect expected")); } } break; case QVariant::RectF: { bool ok = false; - QQmlStringConverters::rectFFromString(binding->valueAsString(qmlUnit), &ok); + QQmlStringConverters::rectFFromString(binding->valueAsString(compilationUnit.data()), &ok); if (!ok) { - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: point expected")); + return warnOrError(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")); + return warnOrError(tr("Invalid property assignment: boolean expected")); } } break; @@ -497,8 +517,8 @@ QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache 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")); + if (!QQmlStringConverters::createFromString(QMetaType::QVector2D, binding->valueAsString(compilationUnit.data()), &vec, sizeof(vec))) { + return warnOrError(tr("Invalid property assignment: 2D vector expected")); } } break; @@ -508,8 +528,8 @@ QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache 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")); + if (!QQmlStringConverters::createFromString(QMetaType::QVector3D, binding->valueAsString(compilationUnit.data()), &vec, sizeof(vec))) { + return warnOrError(tr("Invalid property assignment: 3D vector expected")); } } break; @@ -520,8 +540,8 @@ QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache 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")); + if (!QQmlStringConverters::createFromString(QMetaType::QVector4D, binding->valueAsString(compilationUnit.data()), &vec, sizeof(vec))) { + return warnOrError(tr("Invalid property assignment: 4D vector expected")); } } break; @@ -532,55 +552,59 @@ QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache 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")); + if (!QQmlStringConverters::createFromString(QMetaType::QQuaternion, binding->valueAsString(compilationUnit.data()), &vec, sizeof(vec))) { + return warnOrError(tr("Invalid property assignment: quaternion expected")); } } break; case QVariant::RegExp: - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: regular expression expected; use /pattern/ syntax")); + case QVariant::RegularExpression: + return warnOrError(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")); + return warnOrError(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(); + double n = binding->valueAsNumber(compilationUnit->constants); if (double(int(n)) != n) ok = false; } if (!ok) - return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: int or array of ints expected")); + return warnOrError(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")); + return warnOrError(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")); + return warnOrError(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")); + return warnOrError(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; + } else if (property->isQObject() + && binding->type == QV4::CompiledData::Binding::Type_Null) { + 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())))); + return warnOrError(tr("Invalid property assignment: unsupported type \"%1\"").arg(QString::fromLatin1(QMetaType::typeName(property->propType())))); } } break; @@ -628,21 +652,19 @@ QQmlCompileError QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData * 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 QV4::CompiledData::Object *targetObject = compilationUnit->objectAt(binding->value.objectIndex); + if (auto *typeRef = resolvedType(targetObject->inheritedTypeNameIndex)) { + QQmlRefPointer<QQmlPropertyCache> cache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); const QMetaObject *mo = cache->firstCppMetaObject(); - while (mo && !qmlType) { + QQmlType qmlType; + while (mo && !qmlType.isValid()) { qmlType = QQmlMetaType::qmlType(mo); mo = mo->superClass(); } - Q_ASSERT(qmlType); - } + Q_ASSERT(qmlType.isValid()); - if (qmlType) { - isValueSource = qmlType->propertyValueSourceCast() != -1; - isPropertyInterceptor = qmlType->propertyValueInterceptorCast() != -1; + isValueSource = qmlType.propertyValueSourceCast() != -1; + isPropertyInterceptor = qmlType.propertyValueInterceptorCast() != -1; } if (!isValueSource && !isPropertyInterceptor) { @@ -656,7 +678,7 @@ QQmlCompileError QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData * // 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) { + } else if (property->propType() == QMetaType::QVariant || property->propType() == qMetaTypeId<QJSValue>()) { // We can convert everything to QVariant :) return noError; } else if (property->isQList()) { @@ -668,21 +690,20 @@ QQmlCompileError QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData * } } 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")); + return QQmlCompileError(binding->location, tr("Unexpected object assignment for property \"%1\"").arg(propertyName)); } 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 + // We want to use the 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()); + // Using -1 for the minor version ensures that we get the raw metaObject. + QQmlPropertyCache *propertyMetaObject = enginePrivate->rawPropertyCacheForType(property->propType(), -1); - // Will be true if the assgned type inherits propertyMetaObject + // Will be true if the assigned type inherits propertyMetaObject bool isAssignable = false; // Determine isAssignable value if (propertyMetaObject) { @@ -694,7 +715,8 @@ QQmlCompileError QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData * } if (!isAssignable) { - return QQmlCompileError(binding->valueLocation, tr("Cannot assign object to property")); + return QQmlCompileError(binding->valueLocation, tr("Cannot assign object of type \"%1\" to property of type \"%2\" as the former is neither the same as the latter nor a sub-class of it.") + .arg(stringAt(compilationUnit->objectAt(binding->value.objectIndex)->inheritedTypeNameIndex)).arg(QLatin1String(QMetaType::typeName(property->propType())))); } } return noError; diff --git a/src/qml/compiler/qqmlpropertyvalidator_p.h b/src/qml/compiler/qqmlpropertyvalidator_p.h index e37b8141f4..e9ae844ccb 100644 --- a/src/qml/compiler/qqmlpropertyvalidator_p.h +++ b/src/qml/compiler/qqmlpropertyvalidator_p.h @@ -58,7 +58,7 @@ class QQmlPropertyValidator { Q_DECLARE_TR_FUNCTIONS(QQmlPropertyValidator) public: - QQmlPropertyValidator(QQmlEnginePrivate *enginePrivate, const QQmlImports &imports, QV4::CompiledData::CompilationUnit *compilationUnit); + QQmlPropertyValidator(QQmlEnginePrivate *enginePrivate, const QQmlImports &imports, const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit); QVector<QQmlCompileError> validate(); @@ -71,12 +71,16 @@ private: Q_REQUIRED_RESULT QVector<QQmlCompileError> recordError(const QV4::CompiledData::Location &location, const QString &description) const; Q_REQUIRED_RESULT QVector<QQmlCompileError> recordError(const QQmlCompileError &error) const; - QString stringAt(int index) const { return qmlUnit->stringAt(index); } + QString stringAt(int index) const { return compilationUnit->stringAt(index); } + QV4::CompiledData::ResolvedTypeReference *resolvedType(int id) const + { + return compilationUnit->resolvedType(id); + } QQmlEnginePrivate *enginePrivate; + QQmlRefPointer<QV4::CompiledData::CompilationUnit> compilationUnit; const QQmlImports &imports; const QV4::CompiledData::Unit *qmlUnit; - const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypes; const QQmlPropertyCacheVector &propertyCaches; QVector<QV4::CompiledData::BindingPropertyData> * const bindingPropertyDataPerObject; diff --git a/src/qml/compiler/qqmltypecompiler.cpp b/src/qml/compiler/qqmltypecompiler.cpp index c09fde86f1..70b048d737 100644 --- a/src/qml/compiler/qqmltypecompiler.cpp +++ b/src/qml/compiler/qqmltypecompiler.cpp @@ -44,10 +44,7 @@ #include <private/qqmlcustomparser_p.h> #include <private/qqmlvmemetaobject_p.h> #include <private/qqmlcomponent_p.h> -#include <private/qv4ssa_p.h> - -#include "qqmlpropertycachecreator_p.h" -#include "qv4jssimplifier_p.h" +#include <private/qqmldelegatecomponent_p.h> #define COMPILE_EXCEPTION(token, desc) \ { \ @@ -59,7 +56,7 @@ QT_BEGIN_NAMESPACE QQmlTypeCompiler::QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlTypeData *typeData, QmlIR::Document *parsedQML, const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, - const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache, const QV4::CompiledData::DependentTypesHasher &dependencyHasher) + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache, const QV4::CompiledData::DependentTypesHasher &dependencyHasher) : resolvedTypes(resolvedTypeCache) , engine(engine) , typeData(typeData) @@ -69,19 +66,23 @@ QQmlTypeCompiler::QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlTypeData *type { } -QV4::CompiledData::CompilationUnit *QQmlTypeCompiler::compile() +QQmlRefPointer<QV4::CompiledData::CompilationUnit> QQmlTypeCompiler::compile() { // Build property caches and VME meta object data - for (auto it = resolvedTypes.constBegin(), end = resolvedTypes.constEnd(); + for (auto it = resolvedTypes->constBegin(), end = resolvedTypes->constEnd(); it != end; ++it) { - QQmlCustomParser *customParser = (*it)->type ? (*it)->type->customParser() : 0; + QQmlCustomParser *customParser = (*it)->type.customParser(); if (customParser) customParsers.insert(it.key(), customParser); } + QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings; + + { - QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(&m_propertyCaches, engine, this, imports()); + QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(&m_propertyCaches, &pendingGroupPropertyBindings, + engine, this, imports()); QQmlCompileError error = propertyCacheBuilder.buildMetaObjects(); if (error.isSet()) { recordError(error); @@ -123,6 +124,8 @@ QV4::CompiledData::CompilationUnit *QQmlTypeCompiler::compile() QQmlComponentAndAliasResolver resolver(this); if (!resolver.resolve()) return nullptr; + + pendingGroupPropertyBindings.resolveMissingPropertyCaches(engine, &m_propertyCaches); } { @@ -131,8 +134,8 @@ QV4::CompiledData::CompilationUnit *QQmlTypeCompiler::compile() return nullptr; } - // Compile JS binding expressions and signal handlers if (!document->javaScriptCompilationUnit) { + // Compile JS binding expressions and signal handlers if necessary { // We can compile script strings ahead of time, but they must be compiled // without type optimizations as their scope is always entirely dynamic. @@ -140,37 +143,28 @@ QV4::CompiledData::CompilationUnit *QQmlTypeCompiler::compile() sss.scan(); } - QmlIR::JSCodeGen v4CodeGenerator(typeData->finalUrlString(), document->code, &document->jsModule, &document->jsParserEngine, document->program, typeNameCache, &document->jsGenerator.stringTable); + document->jsModule.fileName = typeData->urlString(); + document->jsModule.finalUrl = typeData->finalUrlString(); + QmlIR::JSCodeGen v4CodeGenerator(document->code, &document->jsGenerator, &document->jsModule, &document->jsParserEngine, + document->program, typeNameCache.data(), &document->jsGenerator.stringTable, engine->v8engine()->illegalNames()); + v4CodeGenerator.setUseFastLookups(false); QQmlJSCodeGenerator jsCodeGen(this, &v4CodeGenerator); if (!jsCodeGen.generateCodeForComponents()) return nullptr; - QQmlJavaScriptBindingExpressionSimplificationPass pass(document->objects, &document->jsModule, &document->jsGenerator); - pass.reduceTranslationBindings(); - - QV4::ExecutionEngine *v4 = engine->v4engine(); - QScopedPointer<QV4::EvalInstructionSelection> isel(v4->iselFactory->create(engine, v4->executableAllocator, &document->jsModule, &document->jsGenerator)); - isel->setUseFastLookups(false); - isel->setUseTypeInference(true); - document->javaScriptCompilationUnit = isel->compile(/*generated unit data*/false); + document->javaScriptCompilationUnit = v4CodeGenerator.generateCompilationUnit(/*generated unit data*/false); } // Generate QML compiled type data structures QmlIR::QmlUnitGenerator qmlGenerator; - QV4::CompiledData::Unit *qmlUnit = qmlGenerator.generate(*document, dependencyHasher); - - Q_ASSERT(document->javaScriptCompilationUnit); - // The js unit owns the data and will free the qml unit. - document->javaScriptCompilationUnit->data = qmlUnit; + qmlGenerator.generate(*document, dependencyHasher); - QV4::CompiledData::CompilationUnit *compilationUnit = document->javaScriptCompilationUnit; - compilationUnit = document->javaScriptCompilationUnit; + QQmlRefPointer<QV4::CompiledData::CompilationUnit> compilationUnit = document->javaScriptCompilationUnit; compilationUnit->typeNameCache = typeNameCache; - compilationUnit->resolvedTypes = resolvedTypes; + compilationUnit->resolvedTypes = *resolvedTypes; compilationUnit->propertyCaches = std::move(m_propertyCaches); - Q_ASSERT(compilationUnit->propertyCaches.count() == static_cast<int>(compilationUnit->data->nObjects)); - + Q_ASSERT(compilationUnit->propertyCaches.count() == static_cast<int>(compilationUnit->objectCount())); if (errors.isEmpty()) return compilationUnit; @@ -209,14 +203,14 @@ int QQmlTypeCompiler::registerString(const QString &str) return document->jsGenerator.registerString(str); } -QV4::IR::Module *QQmlTypeCompiler::jsIRModule() const +int QQmlTypeCompiler::registerConstant(QV4::ReturnedValue v) { - return &document->jsModule; + return document->jsGenerator.registerConstant(v); } const QV4::CompiledData::Unit *QQmlTypeCompiler::qmlUnit() const { - return document->javaScriptCompilationUnit->data; + return document->javaScriptCompilationUnit->unitData(); } const QQmlImports *QQmlTypeCompiler::imports() const @@ -229,15 +223,10 @@ QVector<QmlIR::Object *> *QQmlTypeCompiler::qmlObjects() const return &document->objects; } -int QQmlTypeCompiler::rootObjectIndex() const -{ - return document->indexOfRootObject; -} - void QQmlTypeCompiler::setPropertyCaches(QQmlPropertyCacheVector &&caches) { m_propertyCaches = std::move(caches); - Q_ASSERT(m_propertyCaches.count() >= document->indexOfRootObject); + Q_ASSERT(m_propertyCaches.count() > 0); } const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const @@ -310,8 +299,7 @@ SignalHandlerConverter::SignalHandlerConverter(QQmlTypeCompiler *typeCompiler) , qmlObjects(*typeCompiler->qmlObjects()) , imports(typeCompiler->imports()) , customParsers(typeCompiler->customParserCache()) - , resolvedTypes(typeCompiler->resolvedTypes) - , illegalNames(QV8Engine::get(QQmlEnginePrivate::get(typeCompiler->enginePrivate()))->illegalNames()) + , illegalNames(typeCompiler->enginePrivate()->v8engine()->illegalNames()) , propertyCaches(typeCompiler->propertyCaches()) { } @@ -344,24 +332,22 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio // Attached property? if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { const QmlIR::Object *attachedObj = qmlObjects.at(binding->value.objectIndex); - auto *typeRef = resolvedTypes.value(binding->propertyNameIndex); - QQmlType *type = typeRef ? typeRef->type : 0; - if (!type) { - if (imports->resolveType(propertyName, &type, 0, 0, 0)) { - if (type->isComposite()) { - QQmlTypeData *tdata = enginePrivate->typeLoader.getType(type->sourceUrl()); + auto *typeRef = resolvedType(binding->propertyNameIndex); + QQmlType type = typeRef ? typeRef->type : QQmlType(); + if (!type.isValid()) { + if (imports->resolveType(propertyName, &type, nullptr, nullptr, nullptr)) { + if (type.isComposite()) { + QQmlRefPointer<QQmlTypeData> tdata = enginePrivate->typeLoader.getType(type.sourceUrl()); Q_ASSERT(tdata); Q_ASSERT(tdata->isComplete()); auto compilationUnit = tdata->compilationUnit(); type = QQmlMetaType::qmlType(compilationUnit->metaTypeId); - - tdata->release(); } } } - const QMetaObject *attachedType = type ? type->attachedPropertiesType(enginePrivate) : 0; + const QMetaObject *attachedType = type.attachedPropertiesType(enginePrivate); if (!attachedType) COMPILE_EXCEPTION(binding, tr("Non-existent attached object")); QQmlPropertyCache *cache = compiler->enginePrivate()->cache(attachedType); @@ -412,15 +398,15 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio } else { if (notInRevision) { // Try assinging it as a property later - if (resolver.property(propertyName, /*notInRevision ptr*/0)) + if (resolver.property(propertyName, /*notInRevision ptr*/nullptr)) continue; const QString &originalPropertyName = stringAt(binding->propertyNameIndex); - 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())); + auto *typeRef = resolvedType(obj->inheritedTypeNameIndex); + const QQmlType type = typeRef ? typeRef->type : QQmlType(); + if (type.isValid()) { + 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())); } else { COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available due to component versioning.").arg(typeName).arg(originalPropertyName)); } @@ -472,21 +458,19 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio QQmlJS::MemoryPool *pool = compiler->memoryPool(); - QQmlJS::AST::FormalParameterList *paramList = 0; + QQmlJS::AST::FormalParameterList *paramList = nullptr; for (const QString ¶m : qAsConst(parameters)) { QStringRef paramNameRef = compiler->newStringRef(param); - if (paramList) - paramList = new (pool) QQmlJS::AST::FormalParameterList(paramList, paramNameRef); - else - paramList = new (pool) QQmlJS::AST::FormalParameterList(paramNameRef); + QQmlJS::AST::PatternElement *b = new (pool) QQmlJS::AST::PatternElement(paramNameRef, nullptr); + paramList = new (pool) QQmlJS::AST::FormalParameterList(paramList, b); } if (paramList) - paramList = paramList->finish(); + paramList = paramList->finish(pool); QmlIR::CompiledFunctionOrExpression *foe = obj->functionsAndExpressions->slowAt(binding->value.compiledScriptIndex); - QQmlJS::AST::FunctionDeclaration *functionDeclaration = 0; + QQmlJS::AST::FunctionDeclaration *functionDeclaration = nullptr; if (QQmlJS::AST::ExpressionStatement *es = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement*>(foe->node)) { if (QQmlJS::AST::FunctionExpression *fe = QQmlJS::AST::cast<QQmlJS::AST::FunctionExpression*>(es->expression)) { functionDeclaration = new (pool) QQmlJS::AST::FunctionDeclaration(fe->name, fe->formals, fe->body); @@ -500,14 +484,13 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio } if (!functionDeclaration) { QQmlJS::AST::Statement *statement = static_cast<QQmlJS::AST::Statement*>(foe->node); - QQmlJS::AST::SourceElement *sourceElement = new (pool) QQmlJS::AST::StatementSourceElement(statement); - QQmlJS::AST::SourceElements *elements = new (pool) QQmlJS::AST::SourceElements(sourceElement); - elements = elements->finish(); - - QQmlJS::AST::FunctionBody *body = new (pool) QQmlJS::AST::FunctionBody(elements); + QQmlJS::AST::StatementList *body = new (pool) QQmlJS::AST::StatementList(statement); + body = body->finish(); functionDeclaration = new (pool) QQmlJS::AST::FunctionDeclaration(compiler->newStringRef(stringAt(binding->propertyNameIndex)), paramList, body); - functionDeclaration->functionToken = foe->node->firstSourceLocation(); + functionDeclaration->lbraceToken = functionDeclaration->functionToken + = foe->node->firstSourceLocation(); + functionDeclaration->rbraceToken = foe->node->lastSourceLocation(); } foe->node = functionDeclaration; binding->propertyNameIndex = compiler->registerString(propertyName); @@ -521,7 +504,6 @@ QQmlEnumTypeResolver::QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler) , qmlObjects(*typeCompiler->qmlObjects()) , propertyCaches(typeCompiler->propertyCaches()) , imports(typeCompiler->imports()) - , resolvedTypes(&typeCompiler->resolvedTypes) { } @@ -572,7 +554,8 @@ 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->setNumberValueInternal((double)enumValue); + binding->value.constantValueIndex = compiler->registerConstant(QV4::Encode((double)enumValue)); +// binding->setNumberValueInternal((double)enumValue); binding->flags |= QV4::CompiledData::Binding::IsResolvedEnum; return true; } @@ -623,17 +606,17 @@ bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(const QmlIR::Object *obj, } return true; } - QQmlType *type = 0; - imports->resolveType(typeName, &type, 0, 0, 0); + QQmlType type; + imports->resolveType(typeName, &type, nullptr, nullptr, nullptr); - if (!type && !isQtObject) + if (!type.isValid() && !isQtObject) return true; int value = 0; bool ok = false; - auto *tr = resolvedTypes->value(obj->inheritedTypeNameIndex); - if (type && tr && tr->type == type) { + auto *tr = resolvedType(obj->inheritedTypeNameIndex); + if (type.isValid() && tr && tr->type == type) { // When these two match, we can short cut the search QMetaProperty mprop = propertyCache->firstCppMetaObject()->property(prop->coreIndex()); QMetaEnum menum = mprop.enumerator(); @@ -648,11 +631,11 @@ bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(const QmlIR::Object *obj, } } else { // Otherwise we have to search the whole type - if (type) { + if (type.isValid()) { if (!scopedEnumName.isEmpty()) - value = type->scopedEnumValue(compiler->enginePrivate(), scopedEnumName, enumValue, &ok); + value = type.scopedEnumValue(compiler->enginePrivate(), scopedEnumName, enumValue, &ok); else - value = type->enumValue(compiler->enginePrivate(), QHashedStringRef(enumValue), &ok); + value = type.enumValue(compiler->enginePrivate(), QHashedStringRef(enumValue), &ok); } else { QByteArray enumName = enumValue.toUtf8(); const QMetaObject *metaObject = StaticQtMetaObject::get(); @@ -675,13 +658,13 @@ int QQmlEnumTypeResolver::evaluateEnum(const QString &scope, const QStringRef &e *ok = false; if (scope != QLatin1String("Qt")) { - QQmlType *type = 0; - imports->resolveType(scope, &type, 0, 0, 0); - if (!type) + QQmlType type; + imports->resolveType(scope, &type, nullptr, nullptr, nullptr); + if (!type.isValid()) return -1; if (!enumName.isEmpty()) - return type->scopedEnumValue(compiler->enginePrivate(), enumName, enumValue, ok); - return type->enumValue(compiler->enginePrivate(), QHashedStringRef(enumValue.constData(), enumValue.length()), ok); + return type.scopedEnumValue(compiler->enginePrivate(), enumName, enumValue, ok); + return type.enumValue(compiler->enginePrivate(), QHashedStringRef(enumValue.constData(), enumValue.length()), ok); } const QMetaObject *mo = StaticQtMetaObject::get(); @@ -704,7 +687,7 @@ QQmlCustomParserScriptIndexer::QQmlCustomParserScriptIndexer(QQmlTypeCompiler *t void QQmlCustomParserScriptIndexer::annotateBindingsWithScriptStrings() { - scanObjectRecursively(compiler->rootObjectIndex()); + scanObjectRecursively(/*root object*/0); } void QQmlCustomParserScriptIndexer::scanObjectRecursively(int objectIndex, bool annotateScriptBindings) @@ -799,8 +782,6 @@ QQmlComponentAndAliasResolver::QQmlComponentAndAliasResolver(QQmlTypeCompiler *t , enginePrivate(typeCompiler->enginePrivate()) , pool(typeCompiler->memoryPool()) , qmlObjects(typeCompiler->qmlObjects()) - , indexOfRootObject(typeCompiler->rootObjectIndex()) - , resolvedTypes(&typeCompiler->resolvedTypes) , propertyCaches(std::move(typeCompiler->takePropertyCaches())) { } @@ -818,17 +799,25 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI continue; const QmlIR::Object *targetObject = qmlObjects->at(binding->value.objectIndex); - auto *tr = resolvedTypes->value(targetObject->inheritedTypeNameIndex); + auto *tr = resolvedType(targetObject->inheritedTypeNameIndex); Q_ASSERT(tr); - if (QQmlType *targetType = tr->type) { - if (targetType->metaObject() == &QQmlComponent::staticMetaObject) - continue; - } else if (tr->compilationUnit) { - if (tr->compilationUnit->rootPropertyCache()->firstCppMetaObject() == &QQmlComponent::staticMetaObject) - continue; - } - QQmlPropertyData *pd = 0; + const QMetaObject *firstMetaObject = nullptr; + if (tr->type.isValid()) + firstMetaObject = tr->type.metaObject(); + else if (tr->compilationUnit) + firstMetaObject = tr->compilationUnit->rootPropertyCache()->firstCppMetaObject(); + // 1: test for QQmlComponent + if (firstMetaObject && firstMetaObject == &QQmlComponent::staticMetaObject) + continue; + // 2: test for QQmlAbstractDelegateComponent + while (firstMetaObject && firstMetaObject != &QQmlAbstractDelegateComponent::staticMetaObject) + firstMetaObject = firstMetaObject->superClass(); + if (firstMetaObject) + continue; + // if here, not a QQmlComponent or a QQmlAbstractDelegateComponent, so needs wrapping + + QQmlPropertyData *pd = nullptr; if (binding->propertyNameIndex != quint32(0)) { bool notInRevision = false; pd = propertyResolver.property(stringAt(binding->propertyNameIndex), ¬InRevision); @@ -838,8 +827,8 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI if (!pd || !pd->isQObject()) continue; - QQmlPropertyCache *pc = enginePrivate->rawPropertyCacheForType(pd->propType()); - const QMetaObject *mo = pc ? pc->firstCppMetaObject() : 0; + QQmlPropertyCache *pc = enginePrivate->rawPropertyCacheForType(pd->propType(), pd->typeMinorVersion()); + const QMetaObject *mo = pc ? pc->firstCppMetaObject() : nullptr; while (mo) { if (mo == &QQmlComponent::staticMetaObject) break; @@ -850,23 +839,23 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI 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); + QQmlType componentType = QQmlMetaType::qmlType(&QQmlComponent::staticMetaObject); + Q_ASSERT(componentType.isValid()); const QString qualifier = QStringLiteral("QmlInternals"); - compiler->addImport(componentType->module(), qualifier, componentType->majorVersion(), componentType->minorVersion()); + compiler->addImport(componentType.module(), qualifier, componentType.majorVersion(), componentType.minorVersion()); QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>(); - syntheticComponent->init(pool, compiler->registerString(qualifier + QLatin1Char('.') + componentType->elementName()), 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)) { + if (!containsResolvedType(syntheticComponent->inheritedTypeNameIndex)) { auto typeRef = new QV4::CompiledData::ResolvedTypeReference; typeRef->type = componentType; - typeRef->majorVersion = componentType->majorVersion(); - typeRef->minorVersion = componentType->minorVersion(); - resolvedTypes->insert(syntheticComponent->inheritedTypeNameIndex, typeRef); + typeRef->majorVersion = componentType.majorVersion(); + typeRef->minorVersion = componentType.minorVersion(); + insertResolvedType(syntheticComponent->inheritedTypeNameIndex, typeRef); } qmlObjects->append(syntheticComponent); @@ -904,9 +893,9 @@ bool QQmlComponentAndAliasResolver::resolve() bool isExplicitComponent = false; if (obj->inheritedTypeNameIndex) { - auto *tref = resolvedTypes->value(obj->inheritedTypeNameIndex); + auto *tref = resolvedType(obj->inheritedTypeNameIndex); Q_ASSERT(tref); - if (tref->type && tref->type->metaObject() == &QQmlComponent::staticMetaObject) + if (tref->type.metaObject() == &QQmlComponent::staticMetaObject) isExplicitComponent = true; } if (!isExplicitComponent) { @@ -937,9 +926,9 @@ bool QQmlComponentAndAliasResolver::resolve() if (rootBinding->next || rootBinding->type != QV4::CompiledData::Binding::Type_Object) COMPILE_EXCEPTION(obj, tr("Invalid component body specification")); - // We are going to collect ids/aliases and resolve them for the root object as a separate + // For the root object, we are going to collect ids/aliases and resolve them for as a separate // last pass. - if (i != indexOfRootObject) + if (i != 0) componentRoots.append(i); } @@ -965,12 +954,12 @@ bool QQmlComponentAndAliasResolver::resolve() _idToObjectIndex.clear(); _objectsWithAliases.clear(); - collectIdsAndAliases(indexOfRootObject); + collectIdsAndAliases(/*root object*/0); - QmlIR::Object *rootComponent = qmlObjects->at(indexOfRootObject); + QmlIR::Object *rootComponent = qmlObjects->at(/*root object*/0); rootComponent->namedObjectsInComponent.allocate(pool, _idToObjectIndex); - if (!resolveAliases(indexOfRootObject)) + if (!resolveAliases(/*root object*/0)) return false; // Implicit component insertion may have added objects and thus we also need @@ -998,7 +987,7 @@ bool QQmlComponentAndAliasResolver::collectIdsAndAliases(int objectIndex) _objectsWithAliases.append(objectIndex); // Stop at Component boundary - if (obj->flags & QV4::CompiledData::Object::IsComponent && objectIndex != compiler->rootObjectIndex()) + if (obj->flags & QV4::CompiledData::Object::IsComponent && objectIndex != /*root object*/0) return true; for (const QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { @@ -1037,7 +1026,11 @@ bool QQmlComponentAndAliasResolver::resolveAliases(int componentIndex) } if (result == AllAliasesResolved) { - aliasCacheCreator.appendAliasesToPropertyCache(*qmlObjects->at(componentIndex), objectIndex); + QQmlCompileError error = aliasCacheCreator.appendAliasesToPropertyCache(*qmlObjects->at(componentIndex), objectIndex); + if (error.isSet()) { + recordError(error); + return false; + } atLeastOneAliasResolved = true; } else if (result == SomeAliasesResolved) { atLeastOneAliasResolved = true; @@ -1107,7 +1100,11 @@ QQmlComponentAndAliasResolver::AliasResolutionResult QQmlComponentAndAliasResolv alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; } else { QQmlPropertyCache *targetCache = propertyCaches.at(targetObjectIndex); - Q_ASSERT(targetCache); + if (!targetCache) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(property.toString())); + break; + } + QmlIR::PropertyResolver resolver(targetCache); QQmlPropertyData *targetProperty = resolver.property(property.toString()); @@ -1189,7 +1186,7 @@ QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingSca bool QQmlDeferredAndCustomParserBindingScanner::scanObject() { - return scanObject(compiler->rootObjectIndex()); + return scanObject(/*root object*/0); } bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) @@ -1210,7 +1207,7 @@ bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) return true; QString defaultPropertyName; - QQmlPropertyData *defaultProperty = 0; + QQmlPropertyData *defaultProperty = nullptr; if (obj->indexOfDefaultPropertyOrAlias != -1) { QQmlPropertyCache *cache = propertyCache->parent(); defaultPropertyName = cache->defaultPropertyName(); @@ -1235,7 +1232,7 @@ bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) } for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { - QQmlPropertyData *pd = 0; + QQmlPropertyData *pd = nullptr; QString name = stringAt(binding->propertyNameIndex); if (customParser) { @@ -1275,7 +1272,7 @@ bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) _seenObjectWithId |= seenSubObjectWithId; } - if (!seenSubObjectWithId + if (!seenSubObjectWithId && binding->type != QV4::CompiledData::Binding::Type_GroupProperty && !deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(name)) { binding->flags |= QV4::CompiledData::Binding::IsDeferredBinding; @@ -1299,7 +1296,6 @@ bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) QQmlJSCodeGenerator::QQmlJSCodeGenerator(QQmlTypeCompiler *typeCompiler, QmlIR::JSCodeGen *v4CodeGen) : QQmlCompilePass(typeCompiler) - , resolvedTypes(typeCompiler->resolvedTypes) , customParsers(typeCompiler->customParserCache()) , qmlObjects(*typeCompiler->qmlObjects()) , propertyCaches(typeCompiler->propertyCaches()) @@ -1315,7 +1311,7 @@ bool QQmlJSCodeGenerator::generateCodeForComponents() return false; } - return compileComponent(compiler->rootObjectIndex()); + return compileComponent(/*root object*/0); } bool QQmlJSCodeGenerator::compileComponent(int contextObject) @@ -1329,8 +1325,8 @@ bool QQmlJSCodeGenerator::compileComponent(int contextObject) } QmlIR::JSCodeGen::ObjectIdMapping idMapping; - idMapping.reserve(obj->namedObjectsInComponent.count); - for (int i = 0; i < obj->namedObjectsInComponent.count; ++i) { + idMapping.reserve(obj->namedObjectsInComponent.size()); + for (int i = 0; i < obj->namedObjectsInComponent.size(); ++i) { const int objectIndex = obj->namedObjectsInComponent.at(i); QmlIR::JSCodeGen::IdMapping m; const QmlIR::Object *obj = qmlObjects.at(objectIndex); @@ -1338,9 +1334,9 @@ bool QQmlJSCodeGenerator::compileComponent(int contextObject) m.idIndex = obj->id; m.type = propertyCaches->at(objectIndex); - auto *tref = resolvedTypes.value(obj->inheritedTypeNameIndex); + auto *tref = resolvedType(obj->inheritedTypeNameIndex); if (tref && tref->isFullyDynamicType) - m.type = 0; + m.type = nullptr; idMapping << m; } @@ -1418,10 +1414,10 @@ void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex) QmlIR::Object *object = qmlObjects.at(objectIndex); QString defaultProperty = object->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultPropertyName() : propertyCache->defaultPropertyName(); - QmlIR::Binding *bindingsToReinsert = 0; - QmlIR::Binding *tail = 0; + QmlIR::Binding *bindingsToReinsert = nullptr; + QmlIR::Binding *tail = nullptr; - QmlIR::Binding *previousBinding = 0; + QmlIR::Binding *previousBinding = nullptr; QmlIR::Binding *binding = object->firstBinding(); while (binding) { if (binding->propertyNameIndex == quint32(0) || stringAt(binding->propertyNameIndex) != defaultProperty) { @@ -1440,7 +1436,7 @@ void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex) tail->next = toReinsert; tail = tail->next; } - tail->next = 0; + tail->next = nullptr; } binding = bindingsToReinsert; diff --git a/src/qml/compiler/qqmltypecompiler_p.h b/src/qml/compiler/qqmltypecompiler_p.h index 76aa422fc5..a49b97453f 100644 --- a/src/qml/compiler/qqmltypecompiler_p.h +++ b/src/qml/compiler/qqmltypecompiler_p.h @@ -55,6 +55,7 @@ #include <qhash.h> #include <private/qqmltypeloader_p.h> #include <private/qqmlirbuilder_p.h> +#include <private/qqmlpropertycachecreator_p.h> QT_BEGIN_NAMESPACE @@ -74,22 +75,13 @@ 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, QQmlTypeData *typeData, QmlIR::Document *document, const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache, + QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlTypeData *typeData, QmlIR::Document *document, + const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache, const QV4::CompiledData::DependentTypesHasher &dependencyHasher); // --- interface used by QQmlPropertyCacheCreator @@ -99,10 +91,10 @@ public: 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; + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypes = nullptr; // --- - QV4::CompiledData::CompilationUnit *compile(); + QQmlRefPointer<QV4::CompiledData::CompilationUnit> compile(); QList<QQmlError> compilationErrors() const { return errors; } void recordError(QQmlError error); @@ -110,8 +102,7 @@ public: void recordError(const QQmlCompileError &error); int registerString(const QString &str); - - QV4::IR::Module *jsIRModule() const; + int registerConstant(QV4::ReturnedValue v); const QV4::CompiledData::Unit *qmlUnit() const; @@ -119,7 +110,6 @@ public: QQmlEnginePrivate *enginePrivate() const { return engine; } const QQmlImports *imports() const; QVector<QmlIR::Object *> *qmlObjects() const; - int rootObjectIndex() const; void setPropertyCaches(QQmlPropertyCacheVector &&caches); const QQmlPropertyCacheVector *propertyCaches() const; QQmlPropertyCacheVector &&takePropertyCaches(); @@ -136,6 +126,11 @@ public: void addImport(const QString &module, const QString &qualifier, int majorVersion, int minorVersion); + QV4::CompiledData::ResolvedTypeReference *resolvedType(int id) const + { + return resolvedTypes->value(id); + } + private: QList<QQmlError> errors; QQmlEnginePrivate *engine; @@ -162,6 +157,14 @@ protected: void recordError(const QQmlCompileError &error) { compiler->recordError(error); } + QV4::CompiledData::ResolvedTypeReference *resolvedType(int id) const + { return compiler->resolvedType(id); } + bool containsResolvedType(int id) const + { return compiler->resolvedTypes->contains(id); } + QV4::CompiledData::ResolvedTypeReferenceMap::iterator insertResolvedType( + int id, QV4::CompiledData::ResolvedTypeReference *value) + { return compiler->resolvedTypes->insert(id, value); } + QQmlTypeCompiler *compiler; }; @@ -184,7 +187,6 @@ private: const QVector<QmlIR::Object*> &qmlObjects; const QQmlImports *imports; const QHash<int, QQmlCustomParser*> &customParsers; - const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypes; const QSet<QString> &illegalNames; const QQmlPropertyCacheVector * const propertyCaches; }; @@ -215,7 +217,6 @@ private: const QVector<QmlIR::Object*> &qmlObjects; const QQmlPropertyCacheVector * const propertyCaches; const QQmlImports *imports; - QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypes; }; class QQmlCustomParserScriptIndexer: public QQmlCompilePass @@ -282,7 +283,6 @@ protected: QQmlJS::MemoryPool *pool; QVector<QmlIR::Object*> *qmlObjects; - const int indexOfRootObject; // indices of the objects that are actually Component {} QVector<quint32> componentRoots; @@ -291,7 +291,6 @@ protected: QMap<int, int> _idToObjectIndex; QVector<int> _objectsWithAliases; - QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypes; QQmlPropertyCacheVector propertyCaches; }; @@ -324,7 +323,6 @@ private: bool compileComponent(int componentRoot); bool compileJavaScriptCodeInObjectsRecursively(int objectIndex, int scopeObjectIndex); - const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypes; const QHash<int, QQmlCustomParser*> &customParsers; const QVector<QmlIR::Object*> &qmlObjects; const QQmlPropertyCacheVector * const propertyCaches; diff --git a/src/qml/compiler/qv4bytecodegenerator.cpp b/src/qml/compiler/qv4bytecodegenerator.cpp new file mode 100644 index 0000000000..ea252a6013 --- /dev/null +++ b/src/qml/compiler/qv4bytecodegenerator.cpp @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qv4bytecodegenerator_p.h> +#include <private/qv4compilercontext_p.h> +#include <private/qqmljsastfwd_p.h> + +QT_USE_NAMESPACE +using namespace QV4; +using namespace Moth; + +void BytecodeGenerator::setLocation(const QQmlJS::AST::SourceLocation &loc) +{ + currentLine = static_cast<int>(loc.startLine); +} + +int BytecodeGenerator::newRegister() +{ + int t = currentReg++; + if (regCount < currentReg) + regCount = currentReg; + return t; +} + +int BytecodeGenerator::newRegisterArray(int n) +{ + int t = currentReg; + currentReg += n; + if (regCount < currentReg) + regCount = currentReg; + return t; +} + +void BytecodeGenerator::packInstruction(I &i) +{ + Instr::Type type = Instr::unpack(i.packed); + Q_ASSERT(int(type) < MOTH_NUM_INSTRUCTIONS()); + type = Instr::narrowInstructionType(type); + int instructionsAsInts[sizeof(Instr)/sizeof(int)] = {}; + int nMembers = Moth::InstrInfo::argumentCount[static_cast<int>(i.type)]; + uchar *code = i.packed + Instr::encodedLength(type); + for (int j = 0; j < nMembers; ++j) { + instructionsAsInts[j] = qFromLittleEndian<qint32>(code + j * sizeof(int)); + } + enum { + Normal, + Wide + } width = Normal; + for (int n = 0; n < nMembers; ++n) { + if (width == Normal && (static_cast<qint8>(instructionsAsInts[n]) != instructionsAsInts[n])) { + width = Wide; + break; + } + } + code = i.packed; + switch (width) { + case Normal: + code = Instr::pack(code, type); + for (int n = 0; n < nMembers; ++n) { + qint8 v = static_cast<qint8>(instructionsAsInts[n]); + memcpy(code, &v, 1); + code += 1; + } + i.size = code - i.packed; + if (i.offsetForJump != -1) + i.offsetForJump = i.size - 1; + break; + case Wide: + // nothing to do + break; + } +} + +void BytecodeGenerator::adjustJumpOffsets() +{ + for (int index = 0; index < instructions.size(); ++index) { + auto &i = instructions[index]; + if (i.offsetForJump == -1) // no jump + continue; + Q_ASSERT(i.linkedLabel != -1 && labels.at(i.linkedLabel) != -1); + const auto &linkedInstruction = instructions.at(labels.at(i.linkedLabel)); + qint8 *c = reinterpret_cast<qint8*>(i.packed + i.offsetForJump); + int jumpOffset = linkedInstruction.position - (i.position + i.size); +// qDebug() << "adjusting jump offset for instruction" << index << i.position << i.size << "offsetForJump" << i.offsetForJump << "target" +// << labels.at(i.linkedLabel) << linkedInstruction.position << "jumpOffset" << jumpOffset; + Instr::Type type = Instr::unpack(i.packed); + if (Instr::isWide(type)) { + Q_ASSERT(i.offsetForJump == i.size - 4); + qToLittleEndian<qint32>(jumpOffset, c); + } else { + Q_ASSERT(i.offsetForJump == i.size - 1); + qint8 o = jumpOffset; + Q_ASSERT(o == jumpOffset); + *c = o; + } + } +} + +void BytecodeGenerator::compressInstructions() +{ + // first round: compress all non jump instructions + int position = 0; + for (auto &i : instructions) { + i.position = position; + if (i.offsetForJump == -1) + packInstruction(i); + position += i.size; + } + + adjustJumpOffsets(); + + // compress all jumps + position = 0; + for (auto &i : instructions) { + i.position = position; + if (i.offsetForJump != -1) + packInstruction(i); + position += i.size; + } + + // adjust once again, as the packing above could have changed offsets + adjustJumpOffsets(); +} + +void BytecodeGenerator::finalize(Compiler::Context *context) +{ + compressInstructions(); + + // collect content and line numbers + QByteArray code; + QVector<CompiledData::CodeOffsetToLine> lineNumbers; + currentLine = -1; + Q_UNUSED(startLine); + for (const auto &i : qAsConst(instructions)) { + if (i.line != currentLine) { + currentLine = i.line; + CompiledData::CodeOffsetToLine entry; + entry.codeOffset = code.size(); + entry.line = currentLine; + lineNumbers.append(entry); + } + code.append(reinterpret_cast<const char *>(i.packed), i.size); + } + + context->code = code; + context->lineNumberMapping = lineNumbers; + + for (const auto &li : _labelInfos) { + context->labelInfo.push_back(instructions.at(labels.at(li.labelIndex)).position); + } +} + +int BytecodeGenerator::addInstructionHelper(Instr::Type type, const Instr &i, int offsetOfOffset) { + if (lastInstrType == int(Instr::Type::StoreReg)) { + if (type == Instr::Type::LoadReg) { + if (i.LoadReg.reg == lastInstr.StoreReg.reg) { + // value is already in the accumulator + return -1; + } + } + if (type == Instr::Type::MoveReg) { + if (i.MoveReg.srcReg == lastInstr.StoreReg.reg) { + Instruction::StoreReg store; + store.reg = i.MoveReg.destReg; + addInstruction(store); + return -1; + } + } + } + lastInstrType = int(type); + lastInstr = i; + +#if QT_CONFIG(qml_debug) + if (debugMode && type != Instr::Type::Debug) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // broken gcc warns about Instruction::Debug() + if (instructions.isEmpty() || currentLine != instructions.constLast().line) { + addInstruction(Instruction::Debug()); + } else if (type == Instr::Type::Ret) { + currentLine = -currentLine; + addInstruction(Instruction::Debug()); + currentLine = -currentLine; + } +QT_WARNING_POP + } +#else + Q_UNUSED(debugMode); +#endif + + const int pos = instructions.size(); + + const int argCount = Moth::InstrInfo::argumentCount[static_cast<int>(type)]; + int s = argCount*sizeof(int); + if (offsetOfOffset != -1) + offsetOfOffset += Instr::encodedLength(type); + I instr{type, static_cast<short>(s + Instr::encodedLength(type)), 0, currentLine, offsetOfOffset, -1, "\0\0" }; + uchar *code = instr.packed; + code = Instr::pack(code, Instr::wideInstructionType(type)); + + for (int j = 0; j < argCount; ++j) { + qToLittleEndian<qint32>(i.argumentsAsInts[j], code); + code += sizeof(int); + } + + instructions.append(instr); + + return pos; +} diff --git a/src/qml/compiler/qv4bytecodegenerator_p.h b/src/qml/compiler/qv4bytecodegenerator_p.h new file mode 100644 index 0000000000..1d0a57c536 --- /dev/null +++ b/src/qml/compiler/qv4bytecodegenerator_p.h @@ -0,0 +1,362 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QV4BYTECODEGENERATOR_P_H +#define QV4BYTECODEGENERATOR_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/qv4instr_moth_p.h> +#include <private/qv4compileddata_p.h> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace AST { +class SourceLocation; +} +} + +namespace QV4 { +namespace Moth { + +class BytecodeGenerator { +public: + typedef CompiledData::Function::TraceInfoCount TraceInfoCount; + + BytecodeGenerator(int line, bool debug) + : startLine(line), debugMode(debug) {} + + struct Label { + enum LinkMode { + LinkNow, + LinkLater + }; + Label() = default; + Label(BytecodeGenerator *generator, LinkMode mode = LinkNow) + : generator(generator), + index(generator->labels.size()) { + generator->labels.append(-1); + if (mode == LinkNow) + link(); + } + + void link() { + Q_ASSERT(index >= 0); + Q_ASSERT(generator->labels[index] == -1); + generator->labels[index] = generator->instructions.size(); + generator->clearLastInstruction(); + } + bool isValid() const { return generator != nullptr; } + + BytecodeGenerator *generator = nullptr; + int index = -1; + }; + + struct Jump { + Jump(BytecodeGenerator *generator, int instruction) + : generator(generator), + index(instruction) + { Q_ASSERT(generator && index != -1); } + + ~Jump() { + Q_ASSERT(index == -1 || generator->instructions[index].linkedLabel != -1); // make sure link() got called + } + + Jump(Jump &&j) { + std::swap(generator, j.generator); + std::swap(index, j.index); + } + + BytecodeGenerator *generator = nullptr; + int index = -1; + + void link() { + link(generator->label()); + } + void link(Label l) { + Q_ASSERT(l.index >= 0); + Q_ASSERT(generator->instructions[index].linkedLabel == -1); + generator->instructions[index].linkedLabel = l.index; + } + + private: + // make this type move-only: + Q_DISABLE_COPY(Jump) + // we never move-assign this type anywhere, so disable it: + Jump &operator=(Jump &&) = delete; + }; + + struct ExceptionHandler : public Label { + ExceptionHandler() = default; + ExceptionHandler(BytecodeGenerator *generator) + : Label(generator, LinkLater) + { + } + ~ExceptionHandler() + { + Q_ASSERT(!generator || generator->currentExceptionHandler != this); + } + bool isValid() const { return generator != nullptr; } + }; + + Label label() { + return Label(this, Label::LinkNow); + } + + Label newLabel() { + return Label(this, Label::LinkLater); + } + + ExceptionHandler newExceptionHandler() { + return ExceptionHandler(this); + } + + template<int InstrT> + void addInstruction(const InstrData<InstrT> &data) + { + Instr genericInstr; + InstrMeta<InstrT>::setData(genericInstr, data); + addInstructionHelper(Moth::Instr::Type(InstrT), genericInstr); + } + + // Same as addInstruction, but also add a trace slot. Move only, because the instruction cannot + // be reused afterwards. + template<int InstrT> + void addTracingInstruction(InstrData<InstrT> data) + { + data.traceSlot = nextTraceInfo(); + addInstruction(data); + } + + Q_REQUIRED_RESULT Jump jump() + { +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // broken gcc warns about Instruction::Debug() + Instruction::Jump data; + return addJumpInstruction(data); +QT_WARNING_POP + } + + Q_REQUIRED_RESULT Jump jumpTrue() + { + return addTracingJumpInstruction(Instruction::JumpTrue()); + } + + Q_REQUIRED_RESULT Jump jumpFalse() + { + return addTracingJumpInstruction(Instruction::JumpFalse()); + } + + Q_REQUIRED_RESULT Jump jumpNotUndefined() + { + Instruction::JumpNotUndefined data; + return addJumpInstruction(data); + } + + Q_REQUIRED_RESULT Jump jumpNoException() + { + Instruction::JumpNoException data; + return addJumpInstruction(data); + } + + void jumpStrictEqual(const StackSlot &lhs, const Label &target) + { + Instruction::CmpStrictEqual cmp; + cmp.lhs = lhs; + addInstruction(std::move(cmp)); + addTracingJumpInstruction(Instruction::JumpTrue()).link(target); + } + + void jumpStrictNotEqual(const StackSlot &lhs, const Label &target) + { + Instruction::CmpStrictNotEqual cmp; + cmp.lhs = lhs; + addInstruction(std::move(cmp)); + addTracingJumpInstruction(Instruction::JumpTrue()).link(target); + } + + void setUnwindHandler(ExceptionHandler *handler) + { + currentExceptionHandler = handler; + Instruction::SetUnwindHandler data; + data.offset = 0; + if (!handler) + addInstruction(data); + else + addJumpInstruction(data).link(*handler); + } + + void unwindToLabel(int level, const Label &target) + { + if (level) { + Instruction::UnwindToLabel unwind; + unwind.level = level; + addJumpInstruction(unwind).link(target); + } else { + jump().link(target); + } + } + + + + void setLocation(const QQmlJS::AST::SourceLocation &loc); + + ExceptionHandler *exceptionHandler() const { + return currentExceptionHandler; + } + + int newRegister(); + int newRegisterArray(int n); + int registerCount() const { return regCount; } + int currentRegister() const { return currentReg; } + + void finalize(Compiler::Context *context); + + template<int InstrT> + Jump addTracingJumpInstruction(InstrData<InstrT> &&data) + { + data.traceSlot = nextTraceInfo(); + return addJumpInstruction(data); + } + + template<int InstrT> + Jump addJumpInstruction(const InstrData<InstrT> &data) + { + Instr genericInstr; + InstrMeta<InstrT>::setData(genericInstr, data); + return Jump(this, addInstructionHelper(Moth::Instr::Type(InstrT), genericInstr, offsetof(InstrData<InstrT>, offset))); + } + + void addCJumpInstruction(bool jumpOnFalse, const Label *trueLabel, const Label *falseLabel) + { + if (jumpOnFalse) + addTracingJumpInstruction(Instruction::JumpFalse()).link(*falseLabel); + else + addTracingJumpInstruction(Instruction::JumpTrue()).link(*trueLabel); + } + + void clearLastInstruction() + { + lastInstrType = -1; + } + + TraceInfoCount nextTraceInfo() + { + // If tracing is disabled, use slot 0 to unconditionally store all trace info + if (nTraceInfos == CompiledData::Function::NoTracing()) + return TraceInfoCount(0); + return nTraceInfos++; + } + + void setTracing(bool onoff, int argumentCount) + { + if (onoff) + nTraceInfos = argumentCount; + else + nTraceInfos = CompiledData::Function::NoTracing(); + } + + TraceInfoCount traceInfoCount() const + { + return nTraceInfos; + } + + void addLoopStart(const Label &start) + { + _labelInfos.push_back({ start.index }); + } + +private: + friend struct Jump; + friend struct Label; + friend struct ExceptionHandler; + + int addInstructionHelper(Moth::Instr::Type type, const Instr &i, int offsetOfOffset = -1); + + struct I { + Moth::Instr::Type type; + short size; + uint position; + int line; + int offsetForJump; + int linkedLabel; + unsigned char packed[sizeof(Instr) + 2]; // 2 for instruction type + }; + + void compressInstructions(); + void packInstruction(I &i); + void adjustJumpOffsets(); + + QVector<I> instructions; + QVector<int> labels; + ExceptionHandler *currentExceptionHandler = nullptr; + int regCount = 0; +public: + int currentReg = 0; +private: + int startLine = 0; + int currentLine = 0; + bool debugMode = false; + + int lastInstrType = -1; + Moth::Instr lastInstr; + + TraceInfoCount nTraceInfos = TraceInfoCount(0); + + struct LabelInfo { + int labelIndex; + }; + std::vector<LabelInfo> _labelInfos; +}; + +} +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/compiler/qv4bytecodehandler.cpp b/src/qml/compiler/qv4bytecodehandler.cpp new file mode 100644 index 0000000000..f9f755b8c0 --- /dev/null +++ b/src/qml/compiler/qv4bytecodehandler.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qv4bytecodehandler_p.h> + +QT_USE_NAMESPACE +using namespace QV4; +using namespace Moth; + +ByteCodeHandler::~ByteCodeHandler() +{ +} + +#define DISPATCH_INSTRUCTION(name, nargs, ...) \ + generate_##name( \ + __VA_ARGS__ \ + ); + +#define DECODE_AND_DISPATCH(instr) \ + { \ + INSTR_##instr(MOTH_DECODE_WITH_BASE) \ + Q_UNUSED(base_ptr); \ + _currentOffset = _nextOffset; \ + _nextOffset = code - start; \ + if (startInstruction(Instr::Type::instr) == ProcessInstruction) { \ + INSTR_##instr(DISPATCH) \ + endInstruction(Instr::Type::instr); \ + } \ + continue; \ + } + +void ByteCodeHandler::decode(const char *code, uint len) +{ + MOTH_JUMP_TABLE; + + const char *start = code; + const char *end = code + len; + while (code < end) { + MOTH_DISPATCH() + + FOR_EACH_MOTH_INSTR(DECODE_AND_DISPATCH) + } +} + +#undef DECODE_AND_DISPATCH +#undef DISPATCH_INSTRUCTION diff --git a/src/qml/compiler/qv4bytecodehandler_p.h b/src/qml/compiler/qv4bytecodehandler_p.h new file mode 100644 index 0000000000..f1e7c99447 --- /dev/null +++ b/src/qml/compiler/qv4bytecodehandler_p.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4BYTECODEHANDLER_P_H +#define QV4BYTECODEHANDLER_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/qv4instr_moth_p.h> + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace Moth { + +#define BYTECODE_HANDLER_DEFINE_ARGS(nargs, ...) \ + MOTH_EXPAND_FOR_MSVC(BYTECODE_HANDLER_DEFINE_ARGS##nargs(__VA_ARGS__)) + +#define BYTECODE_HANDLER_DEFINE_ARGS0() +#define BYTECODE_HANDLER_DEFINE_ARGS1(arg) \ + int arg +#define BYTECODE_HANDLER_DEFINE_ARGS2(arg1, arg2) \ + int arg1, \ + int arg2 +#define BYTECODE_HANDLER_DEFINE_ARGS3(arg1, arg2, arg3) \ + int arg1, \ + int arg2, \ + int arg3 +#define BYTECODE_HANDLER_DEFINE_ARGS4(arg1, arg2, arg3, arg4) \ + int arg1, \ + int arg2, \ + int arg3, \ + int arg4 +#define BYTECODE_HANDLER_DEFINE_ARGS5(arg1, arg2, arg3, arg4, arg5) \ + int arg1, \ + int arg2, \ + int arg3, \ + int arg4, \ + int arg5 + +#define BYTECODE_HANDLER_DEFINE_VIRTUAL_BYTECODE_HANDLER_INSTRUCTION(name, nargs, ...) \ + virtual void generate_##name( \ + BYTECODE_HANDLER_DEFINE_ARGS(nargs, __VA_ARGS__) \ + ) = 0; + +#define BYTECODE_HANDLER_DEFINE_VIRTUAL_BYTECODE_HANDLER(instr) \ + INSTR_##instr(BYTECODE_HANDLER_DEFINE_VIRTUAL_BYTECODE_HANDLER) + +class ByteCodeHandler +{ +public: + virtual ~ByteCodeHandler(); + + void decode(const char *code, uint len); + + int currentInstructionOffset() const { return _currentOffset; } + int nextInstructionOffset() const { return _nextOffset; } + int absoluteOffset(int relativeOffset) const + { return nextInstructionOffset() + relativeOffset; } + +protected: + FOR_EACH_MOTH_INSTR(BYTECODE_HANDLER_DEFINE_VIRTUAL_BYTECODE_HANDLER) + + enum Verdict { ProcessInstruction, SkipInstruction }; + virtual Verdict startInstruction(Moth::Instr::Type instr) = 0; + virtual void endInstruction(Moth::Instr::Type instr) = 0; + +private: + int _currentOffset = 0; + int _nextOffset = 0; +}; + +} // Moth namespace +} // QV4 namespace + +QT_END_NAMESPACE + +#endif // QV4BYTECODEHANDLER_P_H diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 693a4230ba..448fbff27b 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQml module of the Qt Toolkit. @@ -39,61 +39,39 @@ #include "qv4codegen_p.h" #include "qv4util_p.h" -#include "qv4engine_p.h" #include <QtCore/QCoreApplication> #include <QtCore/QStringList> -#include <QtCore/QSet> -#include <QtCore/QBuffer> -#include <QtCore/QBitArray> -#include <QtCore/QLinkedList> #include <QtCore/QStack> +#include <QScopeGuard> #include <private/qqmljsast_p.h> #include <private/qv4string_p.h> #include <private/qv4value_p.h> +#include <private/qv4compilercontext_p.h> +#include <private/qv4compilercontrolflow_p.h> +#include <private/qv4bytecodegenerator_p.h> +#include <private/qv4compilerscanfunctions_p.h> #ifndef V4_BOOTSTRAP -#include <qv4context_p.h> +# include <qqmlerror.h> #endif #include <cmath> #include <iostream> +static const bool disable_lookups = false; + #ifdef CONST #undef CONST #endif +QT_USE_NAMESPACE using namespace QV4; -using namespace QQmlJS; -using namespace AST; - -static inline void setLocation(IR::Stmt *s, const SourceLocation &loc) -{ - if (s && loc.isValid()) - s->location = loc; -} - -static bool cjumpCanHandle(IR::AluOp op) -{ - switch (op) { - case IR::OpIn: - case IR::OpInstanceof: - case IR::OpEqual: - case IR::OpNotEqual: - case IR::OpGe: - case IR::OpGt: - case IR::OpLe: - case IR::OpLt: - case IR::OpStrictEqual: - case IR::OpStrictNotEqual: - return true; - default: - return false; - } -} +using namespace QV4::Compiler; +using namespace QQmlJS::AST; -static inline void setJumpOutLocation(IR::Stmt *s, const Statement *body, - const SourceLocation &fallback) +static inline void setJumpOutLocation(QV4::Moth::BytecodeGenerator *bytecodeGenerator, + const Statement *body, const SourceLocation &fallback) { switch (body->kind) { // Statements where we might never execute the last line. @@ -102,677 +80,296 @@ static inline void setJumpOutLocation(IR::Stmt *s, const Statement *body, case Statement::Kind_ForEachStatement: case Statement::Kind_ForStatement: case Statement::Kind_IfStatement: - case Statement::Kind_LocalForEachStatement: - case Statement::Kind_LocalForStatement: case Statement::Kind_WhileStatement: - setLocation(s, fallback); + bytecodeGenerator->setLocation(fallback); break; default: - setLocation(s, body->lastSourceLocation()); + bytecodeGenerator->setLocation(body->lastSourceLocation()); break; } } -Codegen::ScanFunctions::ScanFunctions(Codegen *cg, const QString &sourceCode, CompilationMode defaultProgramMode) - : _cg(cg) - , _sourceCode(sourceCode) - , _variableEnvironment(0) - , _allowFuncDecls(true) - , defaultProgramMode(defaultProgramMode) -{ -} - -void Codegen::ScanFunctions::operator()(Node *node) -{ - if (node) - node->accept(this); -} - -void Codegen::ScanFunctions::enterEnvironment(Node *node, CompilationMode compilationMode) -{ - Environment *e = _cg->newEnvironment(node, _variableEnvironment, compilationMode); - if (!e->isStrict) - e->isStrict = _cg->_strictMode; - _envStack.append(e); - _variableEnvironment = e; -} - -void Codegen::ScanFunctions::leaveEnvironment() -{ - _envStack.pop(); - _variableEnvironment = _envStack.isEmpty() ? 0 : _envStack.top(); -} - -void Codegen::ScanFunctions::checkDirectivePrologue(SourceElements *ast) -{ - for (SourceElements *it = ast; it; it = it->next) { - if (StatementSourceElement *stmt = cast<StatementSourceElement *>(it->element)) { - if (ExpressionStatement *expr = cast<ExpressionStatement *>(stmt->statement)) { - if (StringLiteral *strLit = cast<StringLiteral *>(expr->expression)) { - // Use the source code, because the StringLiteral's - // value might have escape sequences in it, which is not - // allowed. - if (strLit->literalToken.length < 2) - continue; - QStringRef str = _sourceCode.midRef(strLit->literalToken.offset + 1, strLit->literalToken.length - 2); - if (str == QLatin1String("use strict")) { - _variableEnvironment->isStrict = true; - } else { - // TODO: give a warning. - } - continue; - } - } - } - - break; - } -} - -void Codegen::ScanFunctions::checkName(const QStringRef &name, const SourceLocation &loc) -{ - if (_variableEnvironment->isStrict) { - if (name == QLatin1String("implements") - || name == QLatin1String("interface") - || name == QLatin1String("let") - || name == QLatin1String("package") - || name == QLatin1String("private") - || name == QLatin1String("protected") - || name == QLatin1String("public") - || name == QLatin1String("static") - || name == QLatin1String("yield")) { - _cg->throwSyntaxError(loc, QStringLiteral("Unexpected strict mode reserved word")); - } - } -} -void Codegen::ScanFunctions::checkForArguments(AST::FormalParameterList *parameters) -{ - while (parameters) { - if (parameters->name == QLatin1String("arguments")) - _variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectNotUsed; - parameters = parameters->next; - } -} - -bool Codegen::ScanFunctions::visit(Program *ast) -{ - enterEnvironment(ast, defaultProgramMode); - checkDirectivePrologue(ast->elements); - return true; -} - -void Codegen::ScanFunctions::endVisit(Program *) -{ - leaveEnvironment(); -} - -bool Codegen::ScanFunctions::visit(CallExpression *ast) -{ - if (! _variableEnvironment->hasDirectEval) { - if (IdentifierExpression *id = cast<IdentifierExpression *>(ast->base)) { - if (id->name == QLatin1String("eval")) { - if (_variableEnvironment->usesArgumentsObject == Environment::ArgumentsObjectUnknown) - _variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectUsed; - _variableEnvironment->hasDirectEval = true; - } - } - } - int argc = 0; - for (ArgumentList *it = ast->arguments; it; it = it->next) - ++argc; - _variableEnvironment->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, argc); - return true; -} - -bool Codegen::ScanFunctions::visit(NewMemberExpression *ast) -{ - int argc = 0; - for (ArgumentList *it = ast->arguments; it; it = it->next) - ++argc; - _variableEnvironment->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, argc); - return true; -} - -bool Codegen::ScanFunctions::visit(ArrayLiteral *ast) -{ - int index = 0; - for (ElementList *it = ast->elements; it; it = it->next) { - for (Elision *elision = it->elision; elision; elision = elision->next) - ++index; - ++index; - } - if (ast->elision) { - for (Elision *elision = ast->elision->next; elision; elision = elision->next) - ++index; - } - _variableEnvironment->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, index); - return true; -} - -bool Codegen::ScanFunctions::visit(VariableDeclaration *ast) -{ - if (_variableEnvironment->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) - _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Variable name may not be eval or arguments in strict mode")); - checkName(ast->name, ast->identifierToken); - if (ast->name == QLatin1String("arguments")) - _variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectNotUsed; - if (ast->scope == AST::VariableDeclaration::VariableScope::ReadOnlyBlockScope && !ast->expression) { - _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Missing initializer in const declaration")); - return false; - } - QString name = ast->name.toString(); - const Environment::Member *m = 0; - if (_variableEnvironment->memberInfo(name, &m)) { - if (m->isLexicallyScoped() || ast->isLexicallyScoped()) { - _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Identifier %1 has already been declared").arg(name)); - return false; - } - } - _variableEnvironment->enter(ast->name.toString(), ast->expression ? Environment::VariableDefinition : Environment::VariableDeclaration, ast->scope); - return true; -} - -bool Codegen::ScanFunctions::visit(IdentifierExpression *ast) -{ - checkName(ast->name, ast->identifierToken); - if (_variableEnvironment->usesArgumentsObject == Environment::ArgumentsObjectUnknown && ast->name == QLatin1String("arguments")) - _variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectUsed; - return true; -} - -bool Codegen::ScanFunctions::visit(ExpressionStatement *ast) -{ - if (FunctionExpression* expr = AST::cast<AST::FunctionExpression*>(ast->expression)) { - if (!_allowFuncDecls) - _cg->throwSyntaxError(expr->functionToken, QStringLiteral("conditional function or closure declaration")); - - enterFunction(expr, /*enterName*/ true); - Node::accept(expr->formals, this); - Node::accept(expr->body, this); - leaveEnvironment(); - return false; - } else { - SourceLocation firstToken = ast->firstSourceLocation(); - if (_sourceCode.midRef(firstToken.offset, firstToken.length) == QLatin1String("function")) { - _cg->throwSyntaxError(firstToken, QStringLiteral("unexpected token")); - } - } - return true; -} - -bool Codegen::ScanFunctions::visit(FunctionExpression *ast) -{ - enterFunction(ast, /*enterName*/ false); - return true; -} - -void Codegen::ScanFunctions::enterFunction(FunctionExpression *ast, bool enterName, bool isExpression) -{ - if (_variableEnvironment->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) - _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Function name may not be eval or arguments in strict mode")); - enterFunction(ast, ast->name.toString(), ast->formals, ast->body, enterName ? ast : 0, isExpression); -} - -void Codegen::ScanFunctions::endVisit(FunctionExpression *) -{ - leaveEnvironment(); -} - -bool Codegen::ScanFunctions::visit(ObjectLiteral *ast) -{ - int argc = 0; - for (PropertyAssignmentList *it = ast->properties; it; it = it->next) { - QString key = it->assignment->name->asString(); - if (QV4::String::toArrayIndex(key) != UINT_MAX) - ++argc; - ++argc; - if (AST::cast<AST::PropertyGetterSetter *>(it->assignment)) - ++argc; - } - _variableEnvironment->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, argc); - - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true); - Node::accept(ast->properties, this); - return false; -} - -bool Codegen::ScanFunctions::visit(PropertyGetterSetter *ast) -{ - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true); - enterFunction(ast, QString(), ast->formals, ast->functionBody, /*FunctionExpression*/0, /*isExpression*/false); - return true; -} - -void Codegen::ScanFunctions::endVisit(PropertyGetterSetter *) -{ - leaveEnvironment(); -} - -bool Codegen::ScanFunctions::visit(FunctionDeclaration *ast) -{ - enterFunction(ast, /*enterName*/ true, /*isExpression */false); - return true; -} - -void Codegen::ScanFunctions::endVisit(FunctionDeclaration *) -{ - leaveEnvironment(); -} - -bool Codegen::ScanFunctions::visit(WithStatement *ast) -{ - if (_variableEnvironment->isStrict) { - _cg->throwSyntaxError(ast->withToken, QStringLiteral("'with' statement is not allowed in strict mode")); - return false; - } - - return true; -} - -bool Codegen::ScanFunctions::visit(DoWhileStatement *ast) { - { - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict); - Node::accept(ast->statement, this); - } - Node::accept(ast->expression, this); - return false; -} - -bool Codegen::ScanFunctions::visit(ForStatement *ast) { - Node::accept(ast->initialiser, this); - Node::accept(ast->condition, this); - Node::accept(ast->expression, this); - - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict); - Node::accept(ast->statement, this); - - return false; -} - -bool Codegen::ScanFunctions::visit(LocalForStatement *ast) { - Node::accept(ast->declarations, this); - Node::accept(ast->condition, this); - Node::accept(ast->expression, this); - - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict); - Node::accept(ast->statement, this); - - return false; -} - -bool Codegen::ScanFunctions::visit(ForEachStatement *ast) { - Node::accept(ast->initialiser, this); - Node::accept(ast->expression, this); - - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict); - Node::accept(ast->statement, this); - - return false; -} - -bool Codegen::ScanFunctions::visit(LocalForEachStatement *ast) { - Node::accept(ast->declaration, this); - Node::accept(ast->expression, this); - - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict); - Node::accept(ast->statement, this); - - return false; -} - -bool Codegen::ScanFunctions::visit(ThisExpression *) -{ - _variableEnvironment->usesThis = true; - return false; -} - -bool Codegen::ScanFunctions::visit(Block *ast) { - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _variableEnvironment->isStrict ? false : _allowFuncDecls); - Node::accept(ast->statements, this); - return false; -} - -void Codegen::ScanFunctions::enterFunction(Node *ast, const QString &name, FormalParameterList *formals, FunctionBody *body, FunctionExpression *expr, bool isExpression) -{ - bool wasStrict = false; - if (_variableEnvironment) { - _variableEnvironment->hasNestedFunctions = true; - // The identifier of a function expression cannot be referenced from the enclosing environment. - if (expr) - _variableEnvironment->enter(name, Environment::FunctionDefinition, AST::VariableDeclaration::FunctionScope, expr); - if (name == QLatin1String("arguments")) - _variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectNotUsed; - wasStrict = _variableEnvironment->isStrict; - } - - enterEnvironment(ast, FunctionCode); - checkForArguments(formals); - - _variableEnvironment->isNamedFunctionExpression = isExpression && !name.isEmpty(); - _variableEnvironment->formals = formals; - - if (body) - checkDirectivePrologue(body->elements); - - if (wasStrict || _variableEnvironment->isStrict) { - QStringList args; - for (FormalParameterList *it = formals; it; it = it->next) { - QString arg = it->name.toString(); - if (args.contains(arg)) { - _cg->throwSyntaxError(it->identifierToken, QStringLiteral("Duplicate parameter name '%1' is not allowed in strict mode").arg(arg)); - return; - } - if (arg == QLatin1String("eval") || arg == QLatin1String("arguments")) { - _cg->throwSyntaxError(it->identifierToken, QStringLiteral("'%1' cannot be used as parameter name in strict mode").arg(arg)); - return; - } - args += arg; - } - } -} - - -Codegen::Codegen(bool strict) - : _module(0) - , _function(0) - , _block(0) - , _exitBlock(0) - , _returnAddress(0) - , _variableEnvironment(0) - , _loop(0) - , _labelledStatement(0) - , _scopeAndFinally(0) +Codegen::Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict) + : _module(nullptr) + , _returnAddress(-1) + , _context(nullptr) + , _labelledStatement(nullptr) + , jsUnitGenerator(jsUnitGenerator) , _strictMode(strict) , _fileNameIsUrl(false) , hasError(false) { -} + jsUnitGenerator->codeGeneratorName = QStringLiteral("moth"); +} + +const char *globalNames[] = { + "isNaN", + "parseFloat", + "String", + "EvalError", + "URIError", + "Math", + "encodeURIComponent", + "RangeError", + "eval", + "isFinite", + "ReferenceError", + "Infinity", + "Function", + "RegExp", + "Number", + "parseInt", + "Object", + "decodeURI", + "TypeError", + "Boolean", + "encodeURI", + "NaN", + "Error", + "decodeURIComponent", + "Date", + "Array", + "Symbol", + "escape", + "unescape", + "SyntaxError", + "undefined", + "JSON", + "ArrayBuffer", + "SharedArrayBuffer", + "DataView", + "Int8Array", + "Uint8Array", + "Uint8ClampedArray", + "Int16Array", + "Uint16Array", + "Int32Array", + "Uint32Array", + "Float32Array", + "Float64Array", + "WeakSet", + "Set", + "WeakMap", + "Map", + "Reflect", + "Proxy", + "Atomics", + "Promise", + nullptr +}; void Codegen::generateFromProgram(const QString &fileName, + const QString &finalUrl, const QString &sourceCode, Program *node, - QV4::IR::Module *module, - CompilationMode mode, - const QStringList &inheritedLocals) + Module *module, + ContextType contextType) { Q_ASSERT(node); _module = module; - _variableEnvironment = 0; - - _module->setFileName(fileName); + _context = nullptr; + + // ### should be set on the module outside of this method + _module->fileName = fileName; + _module->finalUrl = finalUrl; + + if (contextType == ContextType::ScriptImportedByQML) { + // the global object is frozen, so we know that members of it are + // pointing to the global object. This is important so that references + // to Math etc. do not go through the expensive path in the context wrapper + // that tries to see whether we have a matching type + // + // Since this can be called from the loader thread we can't get the list + // directly from the engine, so let's hardcode the most important ones here + for (const char **g = globalNames; *g != nullptr; ++g) + m_globalNames << QString::fromLatin1(*g); + } - ScanFunctions scan(this, sourceCode, mode); + ScanFunctions scan(this, sourceCode, contextType); scan(node); - defineFunction(QStringLiteral("%entry"), node, 0, node->elements, inheritedLocals); - qDeleteAll(_envMap); - _envMap.clear(); -} - -void Codegen::generateFromFunctionExpression(const QString &fileName, - const QString &sourceCode, - AST::FunctionExpression *ast, - QV4::IR::Module *module) -{ - _module = module; - _module->setFileName(fileName); - _variableEnvironment = 0; - - ScanFunctions scan(this, sourceCode, GlobalCode); - // fake a global environment - scan.enterEnvironment(0, FunctionCode); - scan(ast); - scan.leaveEnvironment(); - - defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0); + if (hasError) + return; - qDeleteAll(_envMap); - _envMap.clear(); + defineFunction(QStringLiteral("%entry"), node, nullptr, node->statements); } - -void Codegen::enterEnvironment(Node *node) +void Codegen::generateFromModule(const QString &fileName, + const QString &finalUrl, + const QString &sourceCode, + ESModule *node, + Module *module) { - _variableEnvironment = _envMap.value(node); - Q_ASSERT(_variableEnvironment); -} + Q_ASSERT(node); -void Codegen::leaveEnvironment() -{ - Q_ASSERT(_variableEnvironment); - _variableEnvironment = _variableEnvironment->parent; -} + _module = module; + _context = nullptr; -void Codegen::enterLoop(Statement *node, IR::BasicBlock *breakBlock, IR::BasicBlock *continueBlock) -{ - _loop = new Loop(node, breakBlock, continueBlock, _loop); - _loop->labelledStatement = _labelledStatement; // consume the enclosing labelled statement - _loop->scopeAndFinally = _scopeAndFinally; - _labelledStatement = 0; -} + // ### should be set on the module outside of this method + _module->fileName = fileName; + _module->finalUrl = finalUrl; -void Codegen::leaveLoop() -{ - Loop *current = _loop; - _loop = _loop->parent; - delete current; -} + ScanFunctions scan(this, sourceCode, ContextType::ESModule); + scan(node); -IR::Expr *Codegen::member(IR::Expr *base, const QString *name) -{ if (hasError) - return 0; - - if (base->asTemp() || base->asArgLocal()) - return _block->MEMBER(base, name); - else { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), base); - return _block->MEMBER(_block->TEMP(t), name); - } -} + return; -IR::Expr *Codegen::subscript(IR::Expr *base, IR::Expr *index) -{ - if (hasError) - return 0; + { + Compiler::Context *moduleContext = _module->contextMap.value(node); + for (const auto &entry: moduleContext->exportEntries) { + if (entry.moduleRequest.isEmpty()) { + // ### check against imported bound names + _module->localExportEntries << entry; + } else if (entry.importName == QLatin1Char('*')) { + _module->starExportEntries << entry; + } else { + _module->indirectExportEntries << entry; + } + } + _module->importEntries = moduleContext->importEntries; - if (! base->asTemp() && !base->asArgLocal()) { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), base); - base = _block->TEMP(t); + _module->moduleRequests = std::move(moduleContext->moduleRequests); + _module->moduleRequests.removeDuplicates(); } - if (! index->asTemp() && !index->asArgLocal() && !index->asConst()) { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), index); - index = _block->TEMP(t); - } + std::sort(_module->localExportEntries.begin(), _module->localExportEntries.end(), ExportEntry::lessThan); + std::sort(_module->starExportEntries.begin(), _module->starExportEntries.end(), ExportEntry::lessThan); + std::sort(_module->indirectExportEntries.begin(), _module->indirectExportEntries.end(), ExportEntry::lessThan); - Q_ASSERT(base->asTemp() || base->asArgLocal()); - Q_ASSERT(index->asTemp() || index->asArgLocal() || index->asConst()); - return _block->SUBSCRIPT(base, index); + defineFunction(QStringLiteral("%entry"), node, nullptr, node->body); } -IR::Expr *Codegen::argument(IR::Expr *expr) +void Codegen::enterContext(Node *node) { - if (expr && !expr->asTemp()) { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), expr); - expr = _block->TEMP(t); - } - return expr; + _context = _module->contextMap.value(node); + Q_ASSERT(_context); } -// keeps references alive, converts other expressions to temps -IR::Expr *Codegen::reference(IR::Expr *expr) +int Codegen::leaveContext() { - if (hasError) - return 0; + Q_ASSERT(_context); + int functionIndex = _context->functionIndex; + _context = _context->parent; + return functionIndex; +} - if (expr && !expr->asTemp() && !expr->asArgLocal() && !expr->asName() && !expr->asMember() && !expr->asSubscript()) { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), expr); - expr = _block->TEMP(t); - } - return expr; +Context *Codegen::enterBlock(Node *node) +{ + enterContext(node); + return _context; } -IR::Expr *Codegen::unop(IR::AluOp op, IR::Expr *expr, const SourceLocation &loc) +Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) { if (hasError) - return 0; - - Q_ASSERT(op != IR::OpIncrement); - Q_ASSERT(op != IR::OpDecrement); + return _expr.result(); - if (IR::Const *c = expr->asConst()) { - if (c->type == IR::NumberType) { + if (expr.isConstant()) { + auto v = Value::fromReturnedValue(expr.constant); + if (v.isNumber()) { switch (op) { - case IR::OpNot: - return _block->CONST(IR::BoolType, !c->value); - case IR::OpUMinus: - return _block->CONST(IR::NumberType, -c->value); - case IR::OpUPlus: + case Not: + return Reference::fromConst(this, Encode(!v.toBoolean())); + case UMinus: + return Reference::fromConst(this, Runtime::UMinus::call(v)); + case UPlus: return expr; - case IR::OpCompl: - return _block->CONST(IR::NumberType, ~QV4::Primitive::toInt32(c->value)); - case IR::OpIncrement: - return _block->CONST(IR::NumberType, c->value + 1); - case IR::OpDecrement: - return _block->CONST(IR::NumberType, c->value - 1); + case Compl: + return Reference::fromConst(this, Encode((int)~v.toInt32())); default: break; } } } - if (!expr->asTemp() && !expr->asArgLocal()) { - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), expr), loc); - expr = _block->TEMP(t); - } - Q_ASSERT(expr->asTemp() || expr->asArgLocal()); - return _block->UNOP(op, expr); -} - -IR::Expr *Codegen::binop(IR::AluOp op, IR::Expr *left, IR::Expr *right, const AST::SourceLocation &loc) -{ - if (hasError) - return 0; - - if (IR::Const *c1 = left->asConst()) { - if (IR::Const *c2 = right->asConst()) { - if ((c1->type & IR::NumberType) && (c2->type & IR::NumberType)) { - switch (op) { - case IR::OpAdd: return _block->CONST(IR::NumberType, c1->value + c2->value); - case IR::OpAnd: return _block->CONST(IR::BoolType, c1->value ? c2->value : 0); - case IR::OpBitAnd: return _block->CONST(IR::NumberType, int(c1->value) & int(c2->value)); - case IR::OpBitOr: return _block->CONST(IR::NumberType, int(c1->value) | int(c2->value)); - case IR::OpBitXor: return _block->CONST(IR::NumberType, int(c1->value) ^ int(c2->value)); - case IR::OpDiv: return _block->CONST(IR::NumberType, c1->value / c2->value); - case IR::OpEqual: return _block->CONST(IR::BoolType, c1->value == c2->value); - case IR::OpNotEqual: return _block->CONST(IR::BoolType, c1->value != c2->value); - case IR::OpStrictEqual: return _block->CONST(IR::BoolType, c1->value == c2->value); - case IR::OpStrictNotEqual: return _block->CONST(IR::BoolType, c1->value != c2->value); - case IR::OpGe: return _block->CONST(IR::BoolType, c1->value >= c2->value); - case IR::OpGt: return _block->CONST(IR::BoolType, c1->value > c2->value); - case IR::OpLe: return _block->CONST(IR::BoolType, c1->value <= c2->value); - case IR::OpLt: return _block->CONST(IR::BoolType, c1->value < c2->value); - case IR::OpLShift: return _block->CONST(IR::NumberType, QV4::Primitive::toInt32(c1->value) << (QV4::Primitive::toUInt32(c2->value) & 0x1f)); - case IR::OpMod: return _block->CONST(IR::NumberType, std::fmod(c1->value, c2->value)); - case IR::OpMul: return _block->CONST(IR::NumberType, c1->value * c2->value); - case IR::OpOr: return _block->CONST(IR::NumberType, c1->value ? c1->value : c2->value); - case IR::OpRShift: return _block->CONST(IR::NumberType, QV4::Primitive::toInt32(c1->value) >> (QV4::Primitive::toUInt32(c2->value) & 0x1f)); - case IR::OpSub: return _block->CONST(IR::NumberType, c1->value - c2->value); - case IR::OpURShift: return _block->CONST(IR::NumberType,QV4::Primitive::toUInt32(c1->value) >> (QV4::Primitive::toUInt32(c2->value) & 0x1f)); - - case IR::OpInstanceof: - case IR::OpIn: - break; - case IR::OpIfTrue: // unary ops - case IR::OpNot: - case IR::OpUMinus: - case IR::OpUPlus: - case IR::OpCompl: - case IR::OpIncrement: - case IR::OpDecrement: - case IR::OpInvalid: - break; - } - } - } - } else if (op == IR::OpAdd) { - if (IR::String *s1 = left->asString()) { - if (IR::String *s2 = right->asString()) { - return _block->STRING(_function->newString(*s1->value + *s2->value)); - } - } + switch (op) { + case UMinus: { + expr.loadInAccumulator(); + Instruction::UMinus uminus = {}; + bytecodeGenerator->addTracingInstruction(uminus); + return Reference::fromAccumulator(this); } - - if (!left->asTemp() && !left->asArgLocal() && !left->asConst()) { - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), left), loc); - left = _block->TEMP(t); + case UPlus: { + expr.loadInAccumulator(); + Instruction::UPlus uplus; + bytecodeGenerator->addInstruction(uplus); + return Reference::fromAccumulator(this); } - - if (!right->asTemp() && !right->asArgLocal() && !right->asConst()) { - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), right), loc); - right = _block->TEMP(t); + case Not: { + expr.loadInAccumulator(); + Instruction::UNot unot; + bytecodeGenerator->addInstruction(unot); + return Reference::fromAccumulator(this); } - - Q_ASSERT(left->asTemp() || left->asArgLocal() || left->asConst()); - Q_ASSERT(right->asTemp() || right->asArgLocal() || right->asConst()); - - return _block->BINOP(op, left, right); -} - -IR::Expr *Codegen::call(IR::Expr *base, IR::ExprList *args) -{ - if (hasError) - return 0; - base = reference(base); - return _block->CALL(base, args); -} - -IR::Stmt *Codegen::move(IR::Expr *target, IR::Expr *source, IR::AluOp op) -{ - if (hasError) - return 0; - - Q_ASSERT(target->isLValue()); - - if (op != IR::OpInvalid) { - return move(target, binop(op, target, source)); + case Compl: { + expr.loadInAccumulator(); + Instruction::UCompl ucompl; + bytecodeGenerator->addInstruction(ucompl); + return Reference::fromAccumulator(this); } - - if (!source->asTemp() && !source->asConst() && !target->asTemp() && !source->asArgLocal() && !target->asArgLocal()) { - unsigned t = _block->newTemp(); - _block->MOVE(_block->TEMP(t), source); - source = _block->TEMP(t); + case PostIncrement: + if (!_expr.accept(nx) || requiresReturnValue) { + Reference e = expr.asLValue(); + e.loadInAccumulator(); + Instruction::UPlus uplus; + bytecodeGenerator->addInstruction(uplus); + Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); + Instruction::Increment inc = {}; + bytecodeGenerator->addTracingInstruction(inc); + e.storeConsumeAccumulator(); + return originalValue; + } else { + // intentionally fall-through: the result is never used, so it's equivalent to + // "expr += 1", which is what a pre-increment does as well. + Q_FALLTHROUGH(); + } + case PreIncrement: { + Reference e = expr.asLValue(); + e.loadInAccumulator(); + Instruction::Increment inc = {}; + bytecodeGenerator->addTracingInstruction(inc); + if (_expr.accept(nx)) + return e.storeConsumeAccumulator(); + else + return e.storeRetainAccumulator(); + } + case PostDecrement: + if (!_expr.accept(nx) || requiresReturnValue) { + Reference e = expr.asLValue(); + e.loadInAccumulator(); + Instruction::UPlus uplus; + bytecodeGenerator->addInstruction(uplus); + Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); + Instruction::Decrement dec = {}; + bytecodeGenerator->addTracingInstruction(dec); + e.storeConsumeAccumulator(); + return originalValue; + } else { + // intentionally fall-through: the result is never used, so it's equivalent to + // "expr -= 1", which is what a pre-decrement does as well. + Q_FALLTHROUGH(); + } + case PreDecrement: { + Reference e = expr.asLValue(); + e.loadInAccumulator(); + Instruction::Decrement dec = {}; + bytecodeGenerator->addTracingInstruction(dec); + if (_expr.accept(nx)) + return e.storeConsumeAccumulator(); + else + return e.storeRetainAccumulator(); } - if (source->asConst() && !target->asTemp() && !target->asArgLocal()) { - unsigned t = _block->newTemp(); - _block->MOVE(_block->TEMP(t), source); - source = _block->TEMP(t); } - return _block->MOVE(target, source); + Q_UNREACHABLE(); } -IR::Stmt *Codegen::cjump(IR::Expr *cond, IR::BasicBlock *iftrue, IR::BasicBlock *iffalse) +void Codegen::addCJump() { - if (hasError) - return 0; - - if (! (cond->asTemp() || (cond->asBinop() && cjumpCanHandle(cond->asBinop()->op)) )) { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), cond); - cond = _block->TEMP(t); - } - return _block->CJUMP(cond, iftrue, iffalse); + bytecodeGenerator->addCJumpInstruction(_expr.trueBlockFollowsCondition(), + _expr.iftrue(), _expr.iffalse()); } void Codegen::accept(Node *node) @@ -786,8 +383,15 @@ void Codegen::accept(Node *node) void Codegen::statement(Statement *ast) { - _block->nextLocation = ast->firstSourceLocation(); + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); + RegisterScope scope(this); + + bytecodeGenerator->setLocation(ast->firstSourceLocation()); + + VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); + qSwap(_volatileMemoryLocations, vLocs); accept(ast); + qSwap(_volatileMemoryLocations, vLocs); } void Codegen::statement(ExpressionNode *ast) @@ -795,296 +399,685 @@ void Codegen::statement(ExpressionNode *ast) if (! ast) { return; } else { + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); + RegisterScope scope(this); + Result r(nx); qSwap(_expr, r); + VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); + qSwap(_volatileMemoryLocations, vLocs); + accept(ast); + + qSwap(_volatileMemoryLocations, vLocs); + qSwap(_expr, r); + if (hasError) return; - qSwap(_expr, r); - if (r.format == ex) { - if (r->asCall()) { - _block->EXP(*r); // the nest nx representation for calls is EXP(CALL(c..)) - } else if (r->asTemp() || r->asArgLocal()) { - // there is nothing to do - } else { - unsigned t = _block->newTemp(); - move(_block->TEMP(t), *r); - } - } + if (r.result().loadTriggersSideEffect()) + r.result().loadInAccumulator(); // triggers side effects } } -void Codegen::condition(ExpressionNode *ast, IR::BasicBlock *iftrue, IR::BasicBlock *iffalse) +void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *iftrue, + const BytecodeGenerator::Label *iffalse, bool trueBlockFollowsCondition) { - if (ast) { - Result r(iftrue, iffalse); - qSwap(_expr, r); - accept(ast); - qSwap(_expr, r); - if (r.format == ex) { - setLocation(cjump(*r, r.iftrue, r.iffalse), ast->firstSourceLocation()); - } + if (hasError) + return; + + if (!ast) + return; + + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); + Result r(iftrue, iffalse, trueBlockFollowsCondition); + qSwap(_expr, r); + accept(ast); + qSwap(_expr, r); + + if (hasError) + return; + + if (r.format() == ex) { + Q_ASSERT(iftrue == r.iftrue()); + Q_ASSERT(iffalse == r.iffalse()); + Q_ASSERT(r.result().isValid()); + bytecodeGenerator->setLocation(ast->firstSourceLocation()); + r.result().loadInAccumulator(); + if (r.trueBlockFollowsCondition()) + bytecodeGenerator->jumpFalse().link(*r.iffalse()); + else + bytecodeGenerator->jumpTrue().link(*r.iftrue()); } } -Codegen::Result Codegen::expression(ExpressionNode *ast) +Codegen::Reference Codegen::expression(ExpressionNode *ast) { + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); Result r; if (ast) { qSwap(_expr, r); accept(ast); qSwap(_expr, r); } - return r; + return r.result(); } -Codegen::Result Codegen::sourceElement(SourceElement *ast) +void Codegen::program(Program *ast) { - Result r(nx); if (ast) { - qSwap(_expr, r); - accept(ast); - qSwap(_expr, r); + statementList(ast->statements); } - return r; } -Codegen::UiMember Codegen::uiObjectMember(UiObjectMember *ast) +enum class CompletionState { + Empty, + EmptyAbrupt, + NonEmpty +}; + +static CompletionState completionState(StatementList *list) +{ + for (StatementList *it = list; it; it = it->next) { + if (it->statement->kind == Statement::Kind_BreakStatement || + it->statement->kind == Statement::Kind_ContinueStatement) + return CompletionState::EmptyAbrupt; + if (it->statement->kind == Statement::Kind_EmptyStatement || + it->statement->kind == Statement::Kind_VariableDeclaration || + it->statement->kind == Statement::Kind_FunctionDeclaration) + continue; + if (it->statement->kind == Statement::Kind_Block) { + CompletionState subState = completionState(static_cast<Block *>(it->statement)->statements); + if (subState != CompletionState::Empty) + return subState; + continue; + } + return CompletionState::NonEmpty; + } + return CompletionState::Empty; +} + +static Node *completionStatement(StatementList *list) +{ + Node *completionStatement = nullptr; + for (StatementList *it = list; it; it = it->next) { + if (it->statement->kind == Statement::Kind_BreakStatement || + it->statement->kind == Statement::Kind_ContinueStatement) + return completionStatement; + if (it->statement->kind == Statement::Kind_ThrowStatement || + it->statement->kind == Statement::Kind_ReturnStatement) + return it->statement; + if (it->statement->kind == Statement::Kind_EmptyStatement || + it->statement->kind == Statement::Kind_VariableStatement || + it->statement->kind == Statement::Kind_FunctionDeclaration) + continue; + if (it->statement->kind == Statement::Kind_Block) { + CompletionState state = completionState(static_cast<Block *>(it->statement)->statements); + switch (state) { + case CompletionState::Empty: + continue; + case CompletionState::EmptyAbrupt: + return it->statement; + case CompletionState::NonEmpty: + break; + } + } + completionStatement = it->statement; + } + return completionStatement; +} + +void Codegen::statementList(StatementList *ast) { - UiMember m; - if (ast) { - qSwap(_uiMember, m); - accept(ast); - qSwap(_uiMember, m); + if (!ast) + return; + + bool _requiresReturnValue = requiresReturnValue; + // ### the next line is pessimizing a bit too much, as there are many cases, where the complietion from the break + // statement will not be used, but it's at least spec compliant + if (!controlFlow || !controlFlow->hasLoop()) + requiresReturnValue = false; + + Node *needsCompletion = nullptr; + + if (_requiresReturnValue && !requiresReturnValue) + needsCompletion = completionStatement(ast); + + if (requiresReturnValue && !needsCompletion && !insideSwitch) { + // break or continue is the first real statement, set the return value to undefined + Reference::fromConst(this, Encode::undefined()).storeOnStack(_returnAddress); + } + + bool _insideSwitch = insideSwitch; + insideSwitch = false; + + for (StatementList *it = ast; it; it = it->next) { + if (it->statement == needsCompletion) + requiresReturnValue = true; + if (Statement *s = it->statement->statementCast()) + statement(s); + else + statement(static_cast<ExpressionNode *>(it->statement)); + if (it->statement == needsCompletion) + requiresReturnValue = false; + if (it->statement->kind == Statement::Kind_ThrowStatement || + it->statement->kind == Statement::Kind_BreakStatement || + it->statement->kind == Statement::Kind_ContinueStatement || + it->statement->kind == Statement::Kind_ReturnStatement) + // any code after those statements is unreachable + break; } - return m; + requiresReturnValue = _requiresReturnValue; + insideSwitch = _insideSwitch; } -void Codegen::functionBody(FunctionBody *ast) +void Codegen::variableDeclaration(PatternElement *ast) { - if (ast) - sourceElements(ast->elements); + TailCallBlocker blockTailCalls(this); + RegisterScope scope(this); + + if (!ast->initializer) { + if (ast->isLexicallyScoped()) { + Reference::fromConst(this, Encode::undefined()).loadInAccumulator(); + Reference varToStore = targetForPatternElement(ast); + varToStore.storeConsumeAccumulator(); + } + return; + } + initializeAndDestructureBindingElement(ast, Reference(), /*isDefinition*/ true); } -void Codegen::program(Program *ast) +void Codegen::variableDeclarationList(VariableDeclarationList *ast) { - if (ast) { - sourceElements(ast->elements); + for (VariableDeclarationList *it = ast; it; it = it->next) { + variableDeclaration(it->declaration); } } -void Codegen::sourceElements(SourceElements *ast) +Codegen::Reference Codegen::targetForPatternElement(AST::PatternElement *p) { - for (SourceElements *it = ast; it; it = it->next) { - sourceElement(it->element); - if (hasError) - return; + if (!p->bindingIdentifier.isNull()) + return referenceForName(p->bindingIdentifier.toString(), true, p->firstSourceLocation()); + if (!p->bindingTarget || p->destructuringPattern()) + return Codegen::Reference::fromStackSlot(this); + Reference lhs = expression(p->bindingTarget); + if (hasError) + return lhs; + if (!lhs.isLValue()) { + throwReferenceError(p->bindingTarget->firstSourceLocation(), QStringLiteral("Binding target is not a reference.")); + return lhs; } + lhs = lhs.asLValue(); + return lhs; } -void Codegen::variableDeclaration(VariableDeclaration *ast) +void Codegen::initializeAndDestructureBindingElement(AST::PatternElement *e, const Reference &base, bool isDefinition) { - IR::Expr *initializer = 0; - if (!ast->expression) - return; - Result expr = expression(ast->expression); + Q_ASSERT(e->type == AST::PatternElement::Binding || e->type == AST::PatternElement::RestElement); + RegisterScope scope(this); + Reference baseRef = (base.isAccumulator()) ? base.storeOnStack() : base; + Reference varToStore = targetForPatternElement(e); + if (isDefinition) + varToStore.isReferenceToConst = false; if (hasError) return; - Q_ASSERT(expr.code); - initializer = *expr; + if (e->initializer) { + if (!baseRef.isValid()) { + // assignment + Reference expr = expression(e->initializer); + if (hasError) + return; + expr.loadInAccumulator(); + varToStore.storeConsumeAccumulator(); + } else if (baseRef == varToStore) { + baseRef.loadInAccumulator(); + BytecodeGenerator::Jump jump = bytecodeGenerator->jumpNotUndefined(); + Reference expr = expression(e->initializer); + if (hasError) { + jump.link(); + return; + } + expr.loadInAccumulator(); + varToStore.storeConsumeAccumulator(); + jump.link(); + } else { + baseRef.loadInAccumulator(); + BytecodeGenerator::Jump jump = bytecodeGenerator->jumpNotUndefined(); + Reference expr = expression(e->initializer); + if (hasError) { + jump.link(); + return; + } + expr.loadInAccumulator(); + jump.link(); + varToStore.storeConsumeAccumulator(); + } + } else if (baseRef != varToStore && baseRef.isValid()) { + baseRef.loadInAccumulator(); + varToStore.storeConsumeAccumulator(); + } + Pattern *p = e->destructuringPattern(); + if (!p) + return; - IR::Expr *lhs = identifier(ast->name.toString(), ast->identifierToken.startLine, - ast->identifierToken.startColumn); + if (!varToStore.isStackSlot()) + varToStore = varToStore.storeOnStack(); + if (PatternElementList *l = e->elementList()) { + destructureElementList(varToStore, l, isDefinition); + } else if (PatternPropertyList *p = e->propertyList()) { + destructurePropertyList(varToStore, p, isDefinition); + } else if (e->bindingTarget) { + // empty binding pattern. For spec compatibility, try to coerce the argument to an object + varToStore.loadInAccumulator(); + Instruction::ToObject toObject; + bytecodeGenerator->addInstruction(toObject); + return; + } +} - if (lhs->asArgLocal()) { - move(lhs, initializer); +Codegen::Reference Codegen::referenceForPropertyName(const Codegen::Reference &object, AST::PropertyName *name) +{ + AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(name); + Reference property; + if (cname) { + Reference computedName = expression(cname->expression); + if (hasError) + return Reference(); + computedName = computedName.storeOnStack(); + property = Reference::fromSubscript(object, computedName).asLValue(); } else { - int initialized = _block->newTemp(); - move(_block->TEMP(initialized), initializer); - move(lhs, _block->TEMP(initialized)); + QString propertyName = name->asString(); + property = Reference::fromMember(object, propertyName); } + return property; } -void Codegen::variableDeclarationList(VariableDeclarationList *ast) +void Codegen::destructurePropertyList(const Codegen::Reference &object, PatternPropertyList *bindingList, bool isDefinition) { - for (VariableDeclarationList *it = ast; it; it = it->next) { - variableDeclaration(it->declaration); + RegisterScope scope(this); + + object.loadInAccumulator(); + Instruction::ThrowOnNullOrUndefined t; + bytecodeGenerator->addInstruction(t); + + for (PatternPropertyList *it = bindingList; it; it = it->next) { + PatternProperty *p = it->property; + RegisterScope scope(this); + Reference property = referenceForPropertyName(object, p->name); + if (hasError) + return; + initializeAndDestructureBindingElement(p, property, isDefinition); + if (hasError) + return; } } +void Codegen::destructureElementList(const Codegen::Reference &array, PatternElementList *bindingList, bool isDefinition) +{ + RegisterScope scope(this); + + Reference iterator = Reference::fromStackSlot(this); + Reference iteratorValue = Reference::fromStackSlot(this); + Reference iteratorDone = Reference::fromStackSlot(this); + Reference::storeConstOnStack(this, Encode(false), iteratorDone.stackSlot()); + + array.loadInAccumulator(); + Instruction::GetIterator iteratorObjInstr; + iteratorObjInstr.iterator = static_cast<int>(AST::ForEachType::Of); + bytecodeGenerator->addInstruction(iteratorObjInstr); + iterator.storeConsumeAccumulator(); + + { + auto cleanup = [this, iterator, iteratorDone]() { + iterator.loadInAccumulator(); + Instruction::IteratorClose close; + close.done = iteratorDone.stackSlot(); + bytecodeGenerator->addInstruction(close); + }; + + ControlFlowUnwindCleanup flow(this, cleanup); + + for (PatternElementList *p = bindingList; p; p = p->next) { + PatternElement *e = p->element; + for (Elision *elision = p->elision; elision; elision = elision->next) { + iterator.loadInAccumulator(); + Instruction::IteratorNext next; + next.value = iteratorValue.stackSlot(); + next.done = iteratorDone.stackSlot(); + bytecodeGenerator->addInstruction(next); + } + + if (!e) + continue; + + RegisterScope scope(this); + iterator.loadInAccumulator(); + + if (e->type == PatternElement::RestElement) { + Reference::fromConst(this, Encode(true)).storeOnStack(iteratorDone.stackSlot()); + bytecodeGenerator->addInstruction(Instruction::DestructureRestElement()); + initializeAndDestructureBindingElement(e, Reference::fromAccumulator(this), isDefinition); + } else { + Instruction::IteratorNext next; + next.value = iteratorValue.stackSlot(); + next.done = iteratorDone.stackSlot(); + bytecodeGenerator->addInstruction(next); + initializeAndDestructureBindingElement(e, iteratorValue, isDefinition); + if (hasError) + return; + } + } + } +} + +void Codegen::destructurePattern(Pattern *p, const Reference &rhs) +{ + RegisterScope scope(this); + if (auto *o = AST::cast<ObjectPattern *>(p)) + destructurePropertyList(rhs, o->properties); + else if (auto *a = AST::cast<ArrayPattern *>(p)) + destructureElementList(rhs, a->elements); + else + Q_UNREACHABLE(); +} + bool Codegen::visit(ArgumentList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(CaseBlock *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(CaseClause *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(CaseClauses *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(Catch *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(DefaultClause *) { - Q_ASSERT(!"unreachable"); - return false; -} - -bool Codegen::visit(ElementList *) -{ - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(Elision *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(Finally *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(FormalParameterList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } -bool Codegen::visit(FunctionBody *) +bool Codegen::visit(Program *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } -bool Codegen::visit(Program *) +bool Codegen::visit(PatternElement *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } -bool Codegen::visit(PropertyAssignmentList *) +bool Codegen::visit(PatternElementList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } -bool Codegen::visit(PropertyNameAndValue *) +bool Codegen::visit(PatternProperty *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } -bool Codegen::visit(PropertyGetterSetter *) +bool Codegen::visit(PatternPropertyList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } -bool Codegen::visit(SourceElements *) +bool Codegen::visit(ExportDeclaration *ast) { - Q_ASSERT(!"unreachable"); + if (!ast->exportDefault) + return true; + + TailCallBlocker blockTailCalls(this); + Reference exportedValue; + + if (auto *fdecl = AST::cast<FunctionDeclaration*>(ast->variableStatementOrDeclaration)) { + Result r; + qSwap(_expr, r); + visit(static_cast<FunctionExpression*>(fdecl)); + qSwap(_expr, r); + exportedValue = r.result(); + } else if (auto *classDecl = AST::cast<ClassDeclaration*>(ast->variableStatementOrDeclaration)) { + Result r; + qSwap(_expr, r); + visit(static_cast<ClassExpression*>(classDecl)); + qSwap(_expr, r); + exportedValue = r.result(); + } else if (ExpressionNode *expr = ast->variableStatementOrDeclaration->expressionCast()) { + exportedValue = expression(expr); + } + + exportedValue.loadInAccumulator(); + + const int defaultExportIndex = _context->locals.indexOf(_context->localNameForDefaultExport); + Q_ASSERT(defaultExportIndex != -1); + Reference defaultExportSlot = Reference::fromScopedLocal(this, defaultExportIndex, /*scope*/0); + defaultExportSlot.storeConsumeAccumulator(); + return false; } bool Codegen::visit(StatementList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(UiArrayMemberList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(UiImport *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(UiHeaderItemList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(UiPragma *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(UiObjectInitializer *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(UiObjectMemberList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(UiParameterList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(UiProgram *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } bool Codegen::visit(UiQualifiedId *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } -bool Codegen::visit(UiQualifiedPragmaId *) +bool Codegen::visit(VariableDeclarationList *) { - Q_ASSERT(!"unreachable"); + Q_UNREACHABLE(); return false; } -bool Codegen::visit(VariableDeclaration *) +bool Codegen::visit(ClassExpression *ast) { - Q_ASSERT(!"unreachable"); + TailCallBlocker blockTailCalls(this); + + Compiler::Class jsClass; + jsClass.nameIndex = registerString(ast->name.toString()); + + ClassElementList *constructor = nullptr; + int nComputedNames = 0; + int nStaticComputedNames = 0; + + RegisterScope scope(this); + ControlFlowBlock controlFlow(this, ast); + + for (auto *member = ast->elements; member; member = member->next) { + PatternProperty *p = member->property; + FunctionExpression *f = p->initializer->asFunctionDefinition(); + Q_ASSERT(f); + AST::ComputedPropertyName *cname = AST::cast<ComputedPropertyName *>(p->name); + if (cname) { + ++nComputedNames; + if (member->isStatic) + ++nStaticComputedNames; + } + QString name = p->name->asString(); + uint nameIndex = cname ? UINT_MAX : registerString(name); + Compiler::Class::Method::Type type = Compiler::Class::Method::Regular; + if (p->type == PatternProperty::Getter) + type = Compiler::Class::Method::Getter; + else if (p->type == PatternProperty::Setter) + type = Compiler::Class::Method::Setter; + Compiler::Class::Method m{ nameIndex, type, static_cast<uint>(defineFunction(name, f, f->formals, f->body)) }; + + if (member->isStatic) { + if (name == QStringLiteral("prototype")) { + throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Cannot declare a static method named 'prototype'.")); + return false; + } + jsClass.staticMethods << m; + } else { + if (name == QStringLiteral("constructor")) { + if (constructor) { + throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Cannot declare a multiple constructors in a class.")); + return false; + } + if (m.type != Compiler::Class::Method::Regular) { + throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Cannot declare a getter or setter named 'constructor'.")); + return false; + } + constructor = member; + jsClass.constructorIndex = m.functionIndex; + continue; + } + + jsClass.methods << m; + } + } + + int classIndex = _module->classes.size(); + _module->classes.append(jsClass); + + Reference heritage = Reference::fromStackSlot(this); + if (ast->heritage) { + bytecodeGenerator->setLocation(ast->heritage->firstSourceLocation()); + Reference r = expression(ast->heritage); + if (hasError) + return false; + r.storeOnStack(heritage.stackSlot()); + } else { + Reference::fromConst(this, Value::emptyValue().asReturnedValue()).loadInAccumulator(); + heritage.storeConsumeAccumulator(); + } + + int computedNames = nComputedNames ? bytecodeGenerator->newRegisterArray(nComputedNames) : 0; + int currentStaticName = computedNames; + int currentNonStaticName = computedNames + nStaticComputedNames; + + for (auto *member = ast->elements; member; member = member->next) { + AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(member->property->name); + if (!cname) + continue; + RegisterScope scope(this); + bytecodeGenerator->setLocation(cname->firstSourceLocation()); + Reference computedName = expression(cname->expression); + if (hasError) + return false; + computedName.storeOnStack(member->isStatic ? currentStaticName++ : currentNonStaticName++); + } + + Instruction::CreateClass createClass; + createClass.classIndex = classIndex; + createClass.heritage = heritage.stackSlot(); + createClass.computedNames = computedNames; + + bytecodeGenerator->addInstruction(createClass); + + if (!ast->name.isEmpty()) { + Reference ctor = referenceForName(ast->name.toString(), true); + ctor.isReferenceToConst = false; // this is the definition + (void) ctor.storeRetainAccumulator(); + } + + _expr.setResult(Reference::fromAccumulator(this)); return false; } -bool Codegen::visit(VariableDeclarationList *) +bool Codegen::visit(ClassDeclaration *ast) { - Q_ASSERT(!"unreachable"); + TailCallBlocker blockTailCalls(this); + Reference outerVar = referenceForName(ast->name.toString(), true); + visit(static_cast<ClassExpression *>(ast)); + (void) outerVar.storeRetainAccumulator(); return false; } @@ -1093,64 +1086,168 @@ bool Codegen::visit(Expression *ast) if (hasError) return false; + TailCallBlocker blockTailCalls(this); statement(ast->left); + blockTailCalls.unblock(); accept(ast->right); return false; } -bool Codegen::visit(ArrayLiteral *ast) +bool Codegen::visit(ArrayPattern *ast) { if (hasError) return false; - IR::ExprList *args = 0; - IR::ExprList *current = 0; - for (ElementList *it = ast->elements; it; it = it->next) { - for (Elision *elision = it->elision; elision; elision = elision->next) { - IR::ExprList *arg = _function->New<IR::ExprList>(); - if (!current) { - args = arg; + TailCallBlocker blockTailCalls(this); + + PatternElementList *it = ast->elements; + + int argc = 0; + { + RegisterScope scope(this); + + int args = -1; + auto push = [this, &argc, &args](AST::ExpressionNode *arg) { + int temp = bytecodeGenerator->newRegister(); + if (args == -1) + args = temp; + if (!arg) { + auto c = Reference::fromConst(this, Value::emptyValue().asReturnedValue()); + (void) c.storeOnStack(temp); } else { - current->next = arg; + RegisterScope scope(this); + Reference r = expression(arg); + if (hasError) + return; + (void) r.storeOnStack(temp); } - current = arg; - current->expr = _block->CONST(IR::MissingType, 0); - } - Result expr = expression(it->expression); - if (hasError) - return false; + ++argc; + }; - IR::ExprList *arg = _function->New<IR::ExprList>(); - if (!current) { - args = arg; - } else { - current->next = arg; + for (; it; it = it->next) { + PatternElement *e = it->element; + if (e && e->type == PatternElement::SpreadElement) + break; + for (Elision *elision = it->elision; elision; elision = elision->next) + push(nullptr); + + if (!e) + continue; + + push(e->initializer); + if (hasError) + return false; } - current = arg; - IR::Expr *exp = *expr; - if (exp->asTemp() || expr->asArgLocal() || exp->asConst()) { - current->expr = exp; - } else { - unsigned value = _block->newTemp(); - move(_block->TEMP(value), exp); - current->expr = _block->TEMP(value); + if (args == -1) { + Q_ASSERT(argc == 0); + args = 0; } + + Instruction::DefineArray call; + call.argc = argc; + call.args = Moth::StackSlot::createRegister(args); + bytecodeGenerator->addInstruction(call); + } + + if (!it) { + _expr.setResult(Reference::fromAccumulator(this)); + return false; } - for (Elision *elision = ast->elision; elision; elision = elision->next) { - IR::ExprList *arg = _function->New<IR::ExprList>(); - if (!current) { - args = arg; + Q_ASSERT(it->element && it->element->type == PatternElement::SpreadElement); + + RegisterScope scope(this); + Reference array = Reference::fromStackSlot(this); + array.storeConsumeAccumulator(); + Reference index = Reference::storeConstOnStack(this, Encode(argc)); + + auto pushAccumulator = [&]() { + Reference slot = Reference::fromSubscript(array, index); + slot.storeConsumeAccumulator(); + + index.loadInAccumulator(); + Instruction::Increment inc = {}; + bytecodeGenerator->addTracingInstruction(inc); + index.storeConsumeAccumulator(); + }; + + while (it) { + for (Elision *elision = it->elision; elision; elision = elision->next) { + Reference::fromConst(this, Value::emptyValue().asReturnedValue()).loadInAccumulator(); + pushAccumulator(); + } + + if (!it->element) { + it = it->next; + continue; + } + + // handle spread element + if (it->element->type == PatternElement::SpreadElement) { + RegisterScope scope(this); + + Reference iterator = Reference::fromStackSlot(this); + Reference iteratorDone = Reference::fromConst(this, Encode(false)).storeOnStack(); + Reference lhsValue = Reference::fromStackSlot(this); + + // There should be a temporal block, so that variables declared in lhs shadow outside vars. + // This block should define a temporal dead zone for those variables, which is not yet implemented. + { + RegisterScope innerScope(this); + Reference expr = expression(it->element->initializer); + if (hasError) + return false; + + expr.loadInAccumulator(); + Instruction::GetIterator iteratorObjInstr; + iteratorObjInstr.iterator = static_cast<int>(AST::ForEachType::Of); + bytecodeGenerator->addInstruction(iteratorObjInstr); + iterator.storeConsumeAccumulator(); + } + + BytecodeGenerator::Label in = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); + + { + auto cleanup = [this, iterator, iteratorDone]() { + iterator.loadInAccumulator(); + Instruction::IteratorClose close; + close.done = iteratorDone.stackSlot(); + bytecodeGenerator->addInstruction(close); + }; + ControlFlowLoop flow(this, &end, &in, cleanup); + + in.link(); + bytecodeGenerator->addLoopStart(in); + iterator.loadInAccumulator(); + Instruction::IteratorNext next; + next.value = lhsValue.stackSlot(); + next.done = iteratorDone.stackSlot(); + bytecodeGenerator->addInstruction(next); + bytecodeGenerator->addTracingJumpInstruction(Instruction::JumpTrue()).link(end); + + lhsValue.loadInAccumulator(); + pushAccumulator(); + + bytecodeGenerator->jump().link(in); + end.link(); + } } else { - current->next = arg; + RegisterScope innerScope(this); + Reference expr = expression(it->element->initializer); + if (hasError) + return false; + + expr.loadInAccumulator(); + pushAccumulator(); } - current = arg; - current->expr = _block->CONST(IR::MissingType, 0); + + it = it->next; } - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), _block->CALL(_block->NAME(IR::Name::builtin_define_array, 0, 0), args)); - _expr.code = _block->TEMP(t); + array.loadInAccumulator(); + _expr.setResult(Reference::fromAccumulator(this)); + return false; } @@ -1159,29 +1256,52 @@ bool Codegen::visit(ArrayMemberExpression *ast) if (hasError) return false; - Result base = expression(ast->base); - Result index = expression(ast->expression); + TailCallBlocker blockTailCalls(this); + Reference base = expression(ast->base); + if (hasError) + return false; + if (base.isSuper()) { + Reference index = expression(ast->expression).storeOnStack(); + _expr.setResult(Reference::fromSuperProperty(index)); + return false; + } + base = base.storeOnStack(); + if (hasError) + return false; + if (AST::StringLiteral *str = AST::cast<AST::StringLiteral *>(ast->expression)) { + QString s = str->value.toString(); + uint arrayIndex = QV4::String::toArrayIndex(s); + if (arrayIndex == UINT_MAX) { + _expr.setResult(Reference::fromMember(base, str->value.toString())); + return false; + } + Reference index = Reference::fromConst(this, QV4::Encode(arrayIndex)); + _expr.setResult(Reference::fromSubscript(base, index)); + return false; + } + Reference index = expression(ast->expression); if (hasError) return false; - _expr.code = subscript(*base, *index); + _expr.setResult(Reference::fromSubscript(base, index)); return false; } -static IR::AluOp baseOp(int op) +static QSOperator::Op baseOp(int op) { switch ((QSOperator::Op) op) { - case QSOperator::InplaceAnd: return IR::OpBitAnd; - case QSOperator::InplaceSub: return IR::OpSub; - case QSOperator::InplaceDiv: return IR::OpDiv; - case QSOperator::InplaceAdd: return IR::OpAdd; - case QSOperator::InplaceLeftShift: return IR::OpLShift; - case QSOperator::InplaceMod: return IR::OpMod; - case QSOperator::InplaceMul: return IR::OpMul; - case QSOperator::InplaceOr: return IR::OpBitOr; - case QSOperator::InplaceRightShift: return IR::OpRShift; - case QSOperator::InplaceURightShift: return IR::OpURShift; - case QSOperator::InplaceXor: return IR::OpBitXor; - default: return IR::OpInvalid; + case QSOperator::InplaceAnd: return QSOperator::BitAnd; + case QSOperator::InplaceSub: return QSOperator::Sub; + case QSOperator::InplaceDiv: return QSOperator::Div; + case QSOperator::InplaceAdd: return QSOperator::Add; + case QSOperator::InplaceLeftShift: return QSOperator::LShift; + case QSOperator::InplaceMod: return QSOperator::Mod; + case QSOperator::InplaceExp: return QSOperator::Exp; + case QSOperator::InplaceMul: return QSOperator::Mul; + case QSOperator::InplaceOr: return QSOperator::BitOr; + case QSOperator::InplaceRightShift: return QSOperator::RShift; + case QSOperator::InplaceURightShift: return QSOperator::URShift; + case QSOperator::InplaceXor: return QSOperator::BitXor; + default: return QSOperator::Invalid; } } @@ -1190,100 +1310,124 @@ bool Codegen::visit(BinaryExpression *ast) if (hasError) return false; + TailCallBlocker blockTailCalls(this); + if (ast->op == QSOperator::And) { if (_expr.accept(cx)) { - IR::BasicBlock *iftrue = _function->newBasicBlock(exceptionHandler()); - condition(ast->left, iftrue, _expr.iffalse); - _block = iftrue; - condition(ast->right, _expr.iftrue, _expr.iffalse); + auto iftrue = bytecodeGenerator->newLabel(); + condition(ast->left, &iftrue, _expr.iffalse(), true); + iftrue.link(); + blockTailCalls.unblock(); + condition(ast->right, _expr.iftrue(), _expr.iffalse(), _expr.trueBlockFollowsCondition()); } else { - IR::BasicBlock *iftrue = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *endif = _function->newBasicBlock(exceptionHandler()); - - const unsigned r = _block->newTemp(); + auto iftrue = bytecodeGenerator->newLabel(); + auto endif = bytecodeGenerator->newLabel(); - Result lhs = expression(ast->left); + Reference left = expression(ast->left); if (hasError) return false; - move(_block->TEMP(r), *lhs); - setLocation(cjump(_block->TEMP(r), iftrue, endif), ast->operatorToken); - _block = iftrue; - Result rhs = expression(ast->right); + left.loadInAccumulator(); + + bytecodeGenerator->setLocation(ast->operatorToken); + bytecodeGenerator->jumpFalse().link(endif); + iftrue.link(); + + blockTailCalls.unblock(); + Reference right = expression(ast->right); if (hasError) return false; - move(_block->TEMP(r), *rhs); - _block->JUMP(endif); + right.loadInAccumulator(); + + endif.link(); - _expr.code = _block->TEMP(r); - _block = endif; + _expr.setResult(Reference::fromAccumulator(this)); } return false; } else if (ast->op == QSOperator::Or) { if (_expr.accept(cx)) { - IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler()); - condition(ast->left, _expr.iftrue, iffalse); - _block = iffalse; - condition(ast->right, _expr.iftrue, _expr.iffalse); + auto iffalse = bytecodeGenerator->newLabel(); + condition(ast->left, _expr.iftrue(), &iffalse, false); + iffalse.link(); + condition(ast->right, _expr.iftrue(), _expr.iffalse(), _expr.trueBlockFollowsCondition()); } else { - IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *endif = _function->newBasicBlock(exceptionHandler()); + auto iffalse = bytecodeGenerator->newLabel(); + auto endif = bytecodeGenerator->newLabel(); - const unsigned r = _block->newTemp(); - Result lhs = expression(ast->left); + Reference left = expression(ast->left); if (hasError) return false; - move(_block->TEMP(r), *lhs); - setLocation(cjump(_block->TEMP(r), endif, iffalse), ast->operatorToken); - _block = iffalse; - Result rhs = expression(ast->right); + left.loadInAccumulator(); + + bytecodeGenerator->setLocation(ast->operatorToken); + bytecodeGenerator->jumpTrue().link(endif); + iffalse.link(); + + blockTailCalls.unblock(); + Reference right = expression(ast->right); + if (hasError) + return false; + right.loadInAccumulator(); + + endif.link(); + + _expr.setResult(Reference::fromAccumulator(this)); + } + return false; + } else if (ast->op == QSOperator::Assign) { + if (AST::Pattern *p = ast->left->patternCast()) { + RegisterScope scope(this); + Reference right = expression(ast->right); if (hasError) return false; - move(_block->TEMP(r), *rhs); - _block->JUMP(endif); + right = right.storeOnStack(); + destructurePattern(p, right); + if (!_expr.accept(nx)) { + right.loadInAccumulator(); + _expr.setResult(Reference::fromAccumulator(this)); + } + return false; + } + Reference left = expression(ast->left); + if (hasError) + return false; - _block = endif; - _expr.code = _block->TEMP(r); + if (!left.isLValue()) { + throwReferenceError(ast->operatorToken, QStringLiteral("left-hand side of assignment operator is not an lvalue")); + return false; } + left = left.asLValue(); + if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation())) + return false; + blockTailCalls.unblock(); + Reference r = expression(ast->right); + if (hasError) + return false; + r.loadInAccumulator(); + if (_expr.accept(nx)) + _expr.setResult(left.storeConsumeAccumulator()); + else + _expr.setResult(left.storeRetainAccumulator()); return false; } - IR::Expr* left = *expression(ast->left); + Reference left = expression(ast->left); if (hasError) return false; switch (ast->op) { case QSOperator::Or: case QSOperator::And: + case QSOperator::Assign: + Q_UNREACHABLE(); // handled separately above break; - case QSOperator::Assign: { - if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation())) - return false; - Result right = expression(ast->right); - if (hasError) - return false; - if (!left->isLValue()) { - throwReferenceError(ast->operatorToken, QStringLiteral("left-hand side of assignment operator is not an lvalue")); - return false; - } - - if (_expr.accept(nx)) { - move(left, *right); - } else { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), *right); - move(left, _block->TEMP(t)); - _expr.code = _block->TEMP(t); - } - break; - } - case QSOperator::InplaceAnd: case QSOperator::InplaceSub: case QSOperator::InplaceDiv: case QSOperator::InplaceAdd: case QSOperator::InplaceLeftShift: case QSOperator::InplaceMod: + case QSOperator::InplaceExp: case QSOperator::InplaceMul: case QSOperator::InplaceOr: case QSOperator::InplaceRightShift: @@ -1291,25 +1435,36 @@ bool Codegen::visit(BinaryExpression *ast) case QSOperator::InplaceXor: { if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation())) return false; - Result right = expression(ast->right); - if (hasError) - return false; - if (!left->isLValue()) { + + if (!left.isLValue()) { throwSyntaxError(ast->operatorToken, QStringLiteral("left-hand side of inplace operator is not an lvalue")); return false; } + left = left.asLValue(); + + Reference tempLeft = left.storeOnStack(); + Reference right = expression(ast->right); + + if (hasError) + return false; + + binopHelper(baseOp(ast->op), tempLeft, right).loadInAccumulator(); + _expr.setResult(left.storeRetainAccumulator()); - if (_expr.accept(nx)) { - move(left, *right, baseOp(ast->op)); - } else { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), *right); - move(left, _block->TEMP(t), baseOp(ast->op)); - _expr.code = left; - } break; } + case QSOperator::BitAnd: + case QSOperator::BitOr: + case QSOperator::BitXor: + if (left.isConstant()) { + Reference right = expression(ast->right); + if (hasError) + return false; + _expr.setResult(binopHelper(static_cast<QSOperator::Op>(ast->op), right, left)); + break; + } + // intentional fall-through! case QSOperator::In: case QSOperator::InstanceOf: case QSOperator::Equal: @@ -1319,53 +1474,417 @@ bool Codegen::visit(BinaryExpression *ast) case QSOperator::Le: case QSOperator::Lt: case QSOperator::StrictEqual: - case QSOperator::StrictNotEqual: { - if (!left->asTemp() && !left->asArgLocal() && !left->asConst()) { - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), left), ast->operatorToken); - left = _block->TEMP(t); + case QSOperator::StrictNotEqual: + case QSOperator::Add: + case QSOperator::Div: + case QSOperator::Exp: + case QSOperator::Mod: + case QSOperator::Mul: + case QSOperator::Sub: + case QSOperator::LShift: + case QSOperator::RShift: + case QSOperator::URShift: { + Reference right; + if (AST::NumericLiteral *rhs = AST::cast<AST::NumericLiteral *>(ast->right)) { + visit(rhs); + right = _expr.result(); + } else { + left = left.storeOnStack(); // force any loads of the lhs, so the rhs won't clobber it + right = expression(ast->right); } - - Result right = expression(ast->right); if (hasError) return false; - if (_expr.accept(cx)) { - setLocation(cjump(binop(IR::binaryOperator(ast->op), left, *right, ast->operatorToken), _expr.iftrue, _expr.iffalse), ast->operatorToken); + _expr.setResult(binopHelper(static_cast<QSOperator::Op>(ast->op), left, right)); + + break; + } + + } // switch + + return false; +} + +Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Reference &right) +{ + switch (oper) { + case QSOperator::Add: { + left = left.storeOnStack(); + right.loadInAccumulator(); + Instruction::Add add; + add.lhs = left.stackSlot(); + bytecodeGenerator->addTracingInstruction(add); + break; + } + case QSOperator::Sub: { + if (right.isConstant() && right.constant == Encode(int(1))) { + left.loadInAccumulator(); + Instruction::Decrement dec = {}; + bytecodeGenerator->addTracingInstruction(dec); } else { - _expr.code = binop(IR::binaryOperator(ast->op), left, *right, ast->operatorToken); + left = left.storeOnStack(); + right.loadInAccumulator(); + Instruction::Sub sub; + sub.lhs = left.stackSlot(); + bytecodeGenerator->addTracingInstruction(sub); } break; } - - case QSOperator::Add: + case QSOperator::Exp: { + left = left.storeOnStack(); + right.loadInAccumulator(); + Instruction::Exp exp; + exp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(exp); + break; + } + case QSOperator::Mul: { + left = left.storeOnStack(); + right.loadInAccumulator(); + Instruction::Mul mul; + mul.lhs = left.stackSlot(); + bytecodeGenerator->addTracingInstruction(mul); + break; + } + case QSOperator::Div: { + left = left.storeOnStack(); + right.loadInAccumulator(); + Instruction::Div div; + div.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(div); + break; + } + case QSOperator::Mod: { + left = left.storeOnStack(); + right.loadInAccumulator(); + Instruction::Mod mod; + mod.lhs = left.stackSlot(); + bytecodeGenerator->addTracingInstruction(mod); + break; + } case QSOperator::BitAnd: + if (right.isConstant()) { + int rightAsInt = Value::fromReturnedValue(right.constant).toInt32(); + if (left.isConstant()) { + int result = Value::fromReturnedValue(left.constant).toInt32() & rightAsInt; + return Reference::fromConst(this, Encode(result)); + } + left.loadInAccumulator(); + Instruction::BitAndConst bitAnd; + bitAnd.rhs = rightAsInt; + bytecodeGenerator->addInstruction(bitAnd); + } else { + right.loadInAccumulator(); + Instruction::BitAnd bitAnd; + bitAnd.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(bitAnd); + } + break; case QSOperator::BitOr: + if (right.isConstant()) { + int rightAsInt = Value::fromReturnedValue(right.constant).toInt32(); + if (left.isConstant()) { + int result = Value::fromReturnedValue(left.constant).toInt32() | rightAsInt; + return Reference::fromConst(this, Encode(result)); + } + left.loadInAccumulator(); + Instruction::BitOrConst bitOr; + bitOr.rhs = rightAsInt; + bytecodeGenerator->addInstruction(bitOr); + } else { + right.loadInAccumulator(); + Instruction::BitOr bitOr; + bitOr.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(bitOr); + } + break; case QSOperator::BitXor: - case QSOperator::Div: - case QSOperator::LShift: - case QSOperator::Mod: - case QSOperator::Mul: + if (right.isConstant()) { + int rightAsInt = Value::fromReturnedValue(right.constant).toInt32(); + if (left.isConstant()) { + int result = Value::fromReturnedValue(left.constant).toInt32() ^ rightAsInt; + return Reference::fromConst(this, Encode(result)); + } + left.loadInAccumulator(); + Instruction::BitXorConst bitXor; + bitXor.rhs = rightAsInt; + bytecodeGenerator->addInstruction(bitXor); + } else { + right.loadInAccumulator(); + Instruction::BitXor bitXor; + bitXor.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(bitXor); + } + break; + case QSOperator::URShift: + if (right.isConstant()) { + left.loadInAccumulator(); + Instruction::UShrConst ushr; + ushr.rhs = Value::fromReturnedValue(right.constant).toInt32() & 0x1f; + bytecodeGenerator->addInstruction(ushr); + } else { + right.loadInAccumulator(); + Instruction::UShr ushr; + ushr.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(ushr); + } + break; case QSOperator::RShift: - case QSOperator::Sub: - case QSOperator::URShift: { - if (!left->asTemp() && !left->asArgLocal() && !left->asConst()) { - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), left), ast->operatorToken); - left = _block->TEMP(t); + if (right.isConstant()) { + left.loadInAccumulator(); + Instruction::ShrConst shr; + shr.rhs = Value::fromReturnedValue(right.constant).toInt32() & 0x1f; + bytecodeGenerator->addInstruction(shr); + } else { + right.loadInAccumulator(); + Instruction::Shr shr; + shr.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(shr); } + break; + case QSOperator::LShift: + if (right.isConstant()) { + left.loadInAccumulator(); + Instruction::ShlConst shl; + shl.rhs = Value::fromReturnedValue(right.constant).toInt32() & 0x1f; + bytecodeGenerator->addInstruction(shl); + } else { + right.loadInAccumulator(); + Instruction::Shl shl; + shl.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(shl); + } + break; + case QSOperator::InstanceOf: { + Instruction::CmpInstanceOf binop; + left = left.storeOnStack(); + right.loadInAccumulator(); + binop.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(binop); + break; + } + case QSOperator::In: { + Instruction::CmpIn binop; + left = left.storeOnStack(); + right.loadInAccumulator(); + binop.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(binop); + break; + } + case QSOperator::StrictEqual: { + if (_expr.accept(cx)) + return jumpBinop(oper, left, right); + + Instruction::CmpStrictEqual cmp; + left = left.storeOnStack(); + right.loadInAccumulator(); + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + break; + } + case QSOperator::StrictNotEqual: { + if (_expr.accept(cx)) + return jumpBinop(oper, left, right); + + Instruction::CmpStrictNotEqual cmp; + left = left.storeOnStack(); + right.loadInAccumulator(); + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + break; + } + case QSOperator::Equal: { + if (_expr.accept(cx)) + return jumpBinop(oper, left, right); + + Instruction::CmpEq cmp; + left = left.storeOnStack(); + right.loadInAccumulator(); + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + break; + } + case QSOperator::NotEqual: { + if (_expr.accept(cx)) + return jumpBinop(oper, left, right); + + Instruction::CmpNe cmp; + left = left.storeOnStack(); + right.loadInAccumulator(); + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + break; + } + case QSOperator::Gt: { + if (_expr.accept(cx)) + return jumpBinop(oper, left, right); + + Instruction::CmpGt cmp; + left = left.storeOnStack(); + right.loadInAccumulator(); + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + break; + } + case QSOperator::Ge: { + if (_expr.accept(cx)) + return jumpBinop(oper, left, right); + + Instruction::CmpGe cmp; + left = left.storeOnStack(); + right.loadInAccumulator(); + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + break; + } + case QSOperator::Lt: { + if (_expr.accept(cx)) + return jumpBinop(oper, left, right); + + Instruction::CmpLt cmp; + left = left.storeOnStack(); + right.loadInAccumulator(); + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + break; + } + case QSOperator::Le: + if (_expr.accept(cx)) + return jumpBinop(oper, left, right); + + Instruction::CmpLe cmp; + left = left.storeOnStack(); + right.loadInAccumulator(); + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + break; + default: + Q_UNREACHABLE(); + } - Result right = expression(ast->right); - if (hasError) - return false; + return Reference::fromAccumulator(this); +} - _expr.code = binop(IR::binaryOperator(ast->op), left, *right, ast->operatorToken); - break; +static QSOperator::Op operatorForSwappedOperands(QSOperator::Op oper) +{ + switch (oper) { + case QSOperator::StrictEqual: return QSOperator::StrictEqual; + case QSOperator::StrictNotEqual: return QSOperator::StrictNotEqual; + case QSOperator::Equal: return QSOperator::Equal; + case QSOperator::NotEqual: return QSOperator::NotEqual; + case QSOperator::Gt: return QSOperator::Le; + case QSOperator::Ge: return QSOperator::Lt; + case QSOperator::Lt: return QSOperator::Ge; + case QSOperator::Le: return QSOperator::Gt; + default: Q_UNIMPLEMENTED(); return QSOperator::Invalid; } +} - } // switch +Codegen::Reference Codegen::jumpBinop(QSOperator::Op oper, Reference &left, Reference &right) +{ + if (left.isConstant()) { + oper = operatorForSwappedOperands(oper); + qSwap(left, right); + } - return false; + if (right.isConstant() && (oper == QSOperator::Equal || oper == QSOperator::NotEqual)) { + Value c = Value::fromReturnedValue(right.constant); + if (c.isNull() || c.isUndefined()) { + left.loadInAccumulator(); + if (oper == QSOperator::Equal) { + Instruction::CmpEqNull cmp; + bytecodeGenerator->addInstruction(cmp); + addCJump(); + return Reference(); + } else if (oper == QSOperator::NotEqual) { + Instruction::CmpNeNull cmp; + bytecodeGenerator->addInstruction(cmp); + addCJump(); + return Reference(); + } + } else if (c.isInt32()) { + left.loadInAccumulator(); + if (oper == QSOperator::Equal) { + Instruction::CmpEqInt cmp; + cmp.lhs = c.int_32(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + return Reference(); + } else if (oper == QSOperator::NotEqual) { + Instruction::CmpNeInt cmp; + cmp.lhs = c.int_32(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + return Reference(); + } + + } + } + + left = left.storeOnStack(); + right.loadInAccumulator(); + + switch (oper) { + case QSOperator::StrictEqual: { + Instruction::CmpStrictEqual cmp; + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + break; + } + case QSOperator::StrictNotEqual: { + Instruction::CmpStrictNotEqual cmp; + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + break; + } + case QSOperator::Equal: { + Instruction::CmpEq cmp; + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + break; + } + case QSOperator::NotEqual: { + Instruction::CmpNe cmp; + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + break; + } + case QSOperator::Gt: { + Instruction::CmpGt cmp; + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + break; + } + case QSOperator::Ge: { + Instruction::CmpGe cmp; + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + break; + } + case QSOperator::Lt: { + Instruction::CmpLt cmp; + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + break; + } + case QSOperator::Le: { + Instruction::CmpLe cmp; + cmp.lhs = left.stackSlot(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + break; + } + default: + Q_UNREACHABLE(); + } + return Reference(); } bool Codegen::visit(CallExpression *ast) @@ -1373,53 +1892,257 @@ bool Codegen::visit(CallExpression *ast) if (hasError) return false; - Result base = expression(ast->base); - IR::ExprList *args = 0, **args_it = &args; - for (ArgumentList *it = ast->arguments; it; it = it->next) { - Result arg = expression(it->expression); - if (hasError) - return false; - IR::Expr *actual = argument(*arg); - *args_it = _function->New<IR::ExprList>(); - (*args_it)->init(actual); - args_it = &(*args_it)->next; + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); + + Reference base = expression(ast->base); + + if (hasError) + return false; + switch (base.type) { + case Reference::Member: + case Reference::Subscript: + case Reference::QmlScopeObject: + case Reference::QmlContextObject: + base = base.asLValue(); + break; + case Reference::Name: + break; + case Reference::Super: + handleConstruct(base, ast->arguments); + return false; + case Reference::SuperProperty: + break; + default: + base = base.storeOnStack(); + break; } + + int thisObject = bytecodeGenerator->newRegister(); + int functionObject = bytecodeGenerator->newRegister(); + + auto calldata = pushArgs(ast->arguments); if (hasError) return false; - _expr.code = call(*base, args); + + blockTailCalls.unblock(); + if (calldata.hasSpread || _tailCallsAreAllowed) { + Reference baseObject = base.baseObject(); + if (!baseObject.isStackSlot()) { + baseObject.storeOnStack(thisObject); + baseObject = Reference::fromStackSlot(this, thisObject); + } + if (!base.isStackSlot()) { + base.storeOnStack(functionObject); + base = Reference::fromStackSlot(this, functionObject); + } + + if (calldata.hasSpread) { + Instruction::CallWithSpread call; + call.func = base.stackSlot(); + call.thisObject = baseObject.stackSlot(); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } else { + Instruction::TailCall call; + call.func = base.stackSlot(); + call.thisObject = baseObject.stackSlot(); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addInstruction(call); + } + + _expr.setResult(Reference::fromAccumulator(this)); + return false; + + } + + handleCall(base, calldata, functionObject, thisObject); return false; } -bool Codegen::visit(ConditionalExpression *ast) +void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject) +{ + //### Do we really need all these call instructions? can's we load the callee in a temp? + if (base.type == Reference::QmlScopeObject) { + Instruction::CallScopeObjectProperty call; + call.base = base.qmlBase.stackSlot(); + call.name = base.qmlCoreIndex; + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } else if (base.type == Reference::QmlContextObject) { + Instruction::CallContextObjectProperty call; + call.base = base.qmlBase.stackSlot(); + call.name = base.qmlCoreIndex; + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } else if (base.type == Reference::Member) { + if (!disable_lookups && useFastLookups) { + Instruction::CallPropertyLookup call; + call.base = base.propertyBase.stackSlot(); + call.lookupIndex = registerGetterLookup(base.propertyNameIndex); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } else { + Instruction::CallProperty call; + call.base = base.propertyBase.stackSlot(); + call.name = base.propertyNameIndex; + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } + } else if (base.type == Reference::Subscript) { + Instruction::CallElement call; + call.base = base.elementBase; + call.index = base.elementSubscript.stackSlot(); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } else if (base.type == Reference::Name) { + if (base.name == QStringLiteral("eval")) { + Instruction::CallPossiblyDirectEval call; + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } else if (!disable_lookups && useFastLookups && base.global) { + Instruction::CallGlobalLookup call; + call.index = registerGlobalGetterLookup(base.nameAsIndex()); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } else { + Instruction::CallName call; + call.name = base.nameAsIndex(); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } + } else if (base.type == Reference::SuperProperty) { + Reference receiver = base.baseObject(); + if (!base.isStackSlot()) { + base.storeOnStack(slotForFunction); + base = Reference::fromStackSlot(this, slotForFunction); + } + if (!receiver.isStackSlot()) { + receiver.storeOnStack(slotForThisObject); + receiver = Reference::fromStackSlot(this, slotForThisObject); + } + Instruction::CallWithReceiver call; + call.name = base.stackSlot(); + call.thisObject = receiver.stackSlot(); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } else { + Q_ASSERT(base.isStackSlot()); + Instruction::CallValue call; + call.name = base.stackSlot(); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addTracingInstruction(call); + } + + _expr.setResult(Reference::fromAccumulator(this)); +} + +Codegen::Arguments Codegen::pushArgs(ArgumentList *args) { - if (hasError) - return true; + bool hasSpread = false; + int argc = 0; + for (ArgumentList *it = args; it; it = it->next) { + if (it->isSpreadElement) { + hasSpread = true; + ++argc; + } + ++argc; + } + + if (!argc) + return { 0, 0, false }; + + int calldata = bytecodeGenerator->newRegisterArray(argc); + + argc = 0; + for (ArgumentList *it = args; it; it = it->next) { + if (it->isSpreadElement) { + Reference::fromConst(this, Value::emptyValue().asReturnedValue()).storeOnStack(calldata + argc); + ++argc; + } + RegisterScope scope(this); + Reference e = expression(it->expression); + if (hasError) + break; + if (!argc && !it->next && !hasSpread) { + // avoid copy for functions taking a single argument + if (e.isStackSlot()) + return { 1, e.stackSlot(), hasSpread }; + } + (void) e.storeOnStack(calldata + argc); + ++argc; + } + + return { argc, calldata, hasSpread }; +} + +Codegen::Arguments Codegen::pushTemplateArgs(TemplateLiteral *args) +{ + int argc = 0; + for (TemplateLiteral *it = args; it; it = it->next) + ++argc; - IR::BasicBlock *iftrue = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *endif = _function->newBasicBlock(exceptionHandler()); + if (!argc) + return { 0, 0, false }; - const unsigned t = _block->newTemp(); + int calldata = bytecodeGenerator->newRegisterArray(argc); - condition(ast->expression, iftrue, iffalse); + argc = 0; + for (TemplateLiteral *it = args; it && it->expression; it = it->next) { + RegisterScope scope(this); + Reference e = expression(it->expression); + if (hasError) + break; + (void) e.storeOnStack(calldata + argc); + ++argc; + } + + return { argc, calldata, false }; +} - _block = iftrue; - Result ok = expression(ast->ok); +bool Codegen::visit(ConditionalExpression *ast) +{ if (hasError) return false; - move(_block->TEMP(t), *ok); - _block->JUMP(endif); - _block = iffalse; - Result ko = expression(ast->ko); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); + + BytecodeGenerator::Label iftrue = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label iffalse = bytecodeGenerator->newLabel(); + condition(ast->expression, &iftrue, &iffalse, true); + + blockTailCalls.unblock(); + + iftrue.link(); + Reference ok = expression(ast->ok); if (hasError) return false; - move(_block->TEMP(t), *ko); - _block->JUMP(endif); + ok.loadInAccumulator(); + BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump(); - _block = endif; + iffalse.link(); + Reference ko = expression(ast->ko); + if (hasError) { + jump_endif.link(); // dummy link, to prevent assert in Jump destructor from triggering + return false; + } + ko.loadInAccumulator(); - _expr.code = _block->TEMP(t); + jump_endif.link(); + _expr.setResult(Reference::fromAccumulator(this)); return false; } @@ -1429,48 +2152,69 @@ bool Codegen::visit(DeleteExpression *ast) if (hasError) return false; - IR::Expr* expr = *expression(ast->expression); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); + Reference expr = expression(ast->expression); if (hasError) return false; - // Temporaries cannot be deleted - IR::ArgLocal *al = expr->asArgLocal(); - if (al && al->index < static_cast<unsigned>(_variableEnvironment->members.size())) { + + switch (expr.type) { + case Reference::SuperProperty: + // ### this should throw a reference error at runtime. + return false; + case Reference::StackSlot: + if (!expr.stackSlotIsLocalOrArgument) + break; + // fall through + case Reference::ScopedLocal: // Trying to delete a function argument might throw. - if (_function->isStrict) { + if (_context->isStrict) { throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode.")); return false; } - _expr.code = _block->CONST(IR::BoolType, 0); + _expr.setResult(Reference::fromConst(this, QV4::Encode(false))); return false; - } - if (_function->isStrict && expr->asName()) { - throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode.")); + case Reference::Name: { + if (_context->isStrict) { + throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode.")); + return false; + } + Instruction::DeleteName del; + del.name = expr.nameAsIndex(); + bytecodeGenerator->addInstruction(del); + _expr.setResult(Reference::fromAccumulator(this)); return false; } - - // [[11.4.1]] Return true if it's not a reference - if (expr->asConst() || expr->asString()) { - _expr.code = _block->CONST(IR::BoolType, 1); + case Reference::Member: { + //### maybe add a variant where the base can be in the accumulator? + expr = expr.asLValue(); + Instruction::LoadRuntimeString instr; + instr.stringId = expr.propertyNameIndex; + bytecodeGenerator->addInstruction(instr); + Reference index = Reference::fromStackSlot(this); + index.storeConsumeAccumulator(); + Instruction::DeleteProperty del; + del.base = expr.propertyBase.stackSlot(); + del.index = index.stackSlot(); + bytecodeGenerator->addInstruction(del); + _expr.setResult(Reference::fromAccumulator(this)); return false; } - - // Return values from calls are also not a reference, but we have to - // perform the call to allow for side effects. - if (expr->asCall()) { - _block->EXP(expr); - _expr.code = _block->CONST(IR::BoolType, 1); + case Reference::Subscript: { + //### maybe add a variant where the index can be in the accumulator? + expr = expr.asLValue(); + Instruction::DeleteProperty del; + del.base = expr.elementBase; + del.index = expr.elementSubscript.stackSlot(); + bytecodeGenerator->addInstruction(del); + _expr.setResult(Reference::fromAccumulator(this)); return false; } - if (expr->asTemp() || - (expr->asArgLocal() && - expr->asArgLocal()->index >= static_cast<unsigned>(_variableEnvironment->members.size()))) { - _expr.code = _block->CONST(IR::BoolType, 1); - return false; + default: + break; } - - IR::ExprList *args = _function->New<IR::ExprList>(); - args->init(reference(expr)); - _expr.code = call(_block->NAME(IR::Name::builtin_delete, ast->deleteToken.startLine, ast->deleteToken.startColumn), args); + // [[11.4.1]] Return true if it's not a reference + _expr.setResult(Reference::fromConst(this, QV4::Encode(true))); return false; } @@ -1479,86 +2223,189 @@ bool Codegen::visit(FalseLiteral *) if (hasError) return false; - if (_expr.accept(cx)) { - _block->JUMP(_expr.iffalse); - } else { - _expr.code = _block->CONST(IR::BoolType, 0); - } + _expr.setResult(Reference::fromConst(this, QV4::Encode(false))); return false; } -bool Codegen::visit(FieldMemberExpression *ast) +bool Codegen::visit(SuperLiteral *) { if (hasError) return false; - Result base = expression(ast->base); - if (!hasError) - _expr.code = member(*base, _function->newString(ast->name.toString())); + _expr.setResult(Reference::fromSuper(this)); return false; } -bool Codegen::visit(FunctionExpression *ast) +bool Codegen::visit(FieldMemberExpression *ast) { if (hasError) return false; - int function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0); - _expr.code = _block->CLOSURE(function); + TailCallBlocker blockTailCalls(this); + if (AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(ast->base)) { + if (id->name == QLatin1String("new")) { + // new.target + Q_ASSERT(ast->name == QLatin1String("target")); + + if (_context->isArrowFunction || _context->contextType == ContextType::Eval) { + Reference r = referenceForName(QStringLiteral("new.target"), false); + r.isReadonly = true; + _expr.setResult(r); + return false; + } + + Reference r = Reference::fromStackSlot(this, CallData::NewTarget); + _expr.setResult(r); + return false; + } + } + + Reference base = expression(ast->base); + if (hasError) + return false; + if (base.isSuper()) { + Instruction::LoadRuntimeString load; + load.stringId = registerString(ast->name.toString()); + bytecodeGenerator->addInstruction(load); + Reference property = Reference::fromAccumulator(this).storeOnStack(); + _expr.setResult(Reference::fromSuperProperty(property)); + return false; + } + _expr.setResult(Reference::fromMember(base, ast->name.toString())); return false; } -IR::Expr *Codegen::identifier(const QString &name, int line, int col) +bool Codegen::visit(TaggedTemplate *ast) { if (hasError) - return 0; + return false; - uint scope = 0; - Environment *e = _variableEnvironment; - IR::Function *f = _function; + RegisterScope scope(this); - while (f && e->parent) { - if (f->insideWithOrCatch || (f->isNamedExpression && QStringRef(f->name) == name)) - return _block->NAME(name, line, col); + int functionObject = -1, thisObject = -1; - int index = e->findMember(name); - Q_ASSERT (index < e->members.size()); - if (index != -1) { - IR::ArgLocal *al = _block->LOCAL(index, scope); - if (name == QLatin1String("arguments") || name == QLatin1String("eval")) - al->isArgumentsOrEval = true; - return al; - } - const int argIdx = f->indexOfArgument(QStringRef(&name)); - if (argIdx != -1) - return _block->ARG(argIdx, scope); + Reference base = expression(ast->base); + if (hasError) + return false; + switch (base.type) { + case Reference::Member: + case Reference::Subscript: + base = base.asLValue(); + break; + case Reference::Name: + break; + case Reference::SuperProperty: + thisObject = bytecodeGenerator->newRegister(); + functionObject = bytecodeGenerator->newRegister(); + break; + default: + base = base.storeOnStack(); + break; + } + + createTemplateObject(ast->templateLiteral); + int templateObjectTemp = Reference::fromAccumulator(this).storeOnStack().stackSlot(); + Q_UNUSED(templateObjectTemp); + auto calldata = pushTemplateArgs(ast->templateLiteral); + if (hasError) + return false; + ++calldata.argc; + Q_ASSERT(calldata.argv == templateObjectTemp + 1); + --calldata.argv; + + handleCall(base, calldata, functionObject, thisObject); + return false; +} + +void Codegen::createTemplateObject(TemplateLiteral *t) +{ + TemplateObject obj; + + for (TemplateLiteral *it = t; it; it = it->next) { + obj.strings.append(registerString(it->value.toString())); + obj.rawStrings.append(registerString(it->rawValue.toString())); + } + + int index = _module->templateObjects.size(); + _module->templateObjects.append(obj); + + Instruction::GetTemplateObject getTemplateObject; + getTemplateObject.index = index; + bytecodeGenerator->addInstruction(getTemplateObject); +} + +bool Codegen::visit(FunctionExpression *ast) +{ + if (hasError) + return false; + + TailCallBlocker blockTailCalls(this); + + RegisterScope scope(this); - if (!f->isStrict && f->hasDirectEval) - return _block->NAME(name, line, col); + int function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body); + if (hasError) + return false; + loadClosure(function); + _expr.setResult(Reference::fromAccumulator(this)); + return false; +} - ++scope; - e = e->parent; - f = f->outer; +Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs, const SourceLocation &accessLocation) +{ + Context::ResolvedName resolved = _context->resolveName(name, accessLocation); + + if (resolved.type == Context::ResolvedName::Local || resolved.type == Context::ResolvedName::Stack + || resolved.type == Context::ResolvedName::Import) { + if (resolved.isArgOrEval && isLhs) + // ### add correct source location + throwSyntaxError(SourceLocation(), QStringLiteral("Variable name may not be eval or arguments in strict mode")); + Reference r; + switch (resolved.type) { + case Context::ResolvedName::Local: + r = Reference::fromScopedLocal(this, resolved.index, resolved.scope); break; + case Context::ResolvedName::Stack: + r = Reference::fromStackSlot(this, resolved.index, true /*isLocal*/); break; + case Context::ResolvedName::Import: + r = Reference::fromImport(this, resolved.index); break; + default: Q_UNREACHABLE(); + } + if (r.isStackSlot() && _volatileMemoryLocations.isVolatile(name)) + r.isVolatile = true; + r.isArgOrEval = resolved.isArgOrEval; + r.isReferenceToConst = resolved.isConst; + r.requiresTDZCheck = resolved.requiresTDZCheck; + r.name = name; // used to show correct name at run-time when TDZ check fails. + return r; } // This hook allows implementing QML lookup semantics - if (IR::Expr *fallback = fallbackNameLookup(name, line, col)) + Reference fallback = fallbackNameLookup(name); + if (fallback.type != Reference::Invalid) return fallback; - if (!e->parent && (!f || !f->insideWithOrCatch) && _variableEnvironment->compilationMode != EvalCode && e->compilationMode != QmlBinding) - return _block->GLOBALNAME(name, line, col); - - // global context or with. Lookup by name - return _block->NAME(name, line, col); + Reference r = Reference::fromName(this, name); + r.global = useFastLookups && (resolved.type == Context::ResolvedName::Global); + if (!r.global && canAccelerateGlobalLookups() && m_globalNames.contains(name)) + r.global = true; + return r; +} +void Codegen::loadClosure(int closureId) +{ + if (closureId >= 0) { + Instruction::LoadClosure load; + load.value = closureId; + bytecodeGenerator->addInstruction(load); + } else { + Reference::fromConst(this, Encode::undefined()).loadInAccumulator(); + } } -IR::Expr *Codegen::fallbackNameLookup(const QString &name, int line, int col) +Codegen::Reference Codegen::fallbackNameLookup(const QString &name) { Q_UNUSED(name) - Q_UNUSED(line) - Q_UNUSED(col) - return 0; + return Reference(); } bool Codegen::visit(IdentifierExpression *ast) @@ -1566,7 +2413,7 @@ bool Codegen::visit(IdentifierExpression *ast) if (hasError) return false; - _expr.code = identifier(ast->name.toString(), ast->identifierToken.startLine, ast->identifierToken.startColumn); + _expr.setResult(referenceForName(ast->name.toString(), false, ast->firstSourceLocation())); return false; } @@ -1579,21 +2426,63 @@ bool Codegen::visit(NestedExpression *ast) return false; } +void Codegen::handleConstruct(const Reference &base, ArgumentList *arguments) +{ + Reference constructor; + if (base.isSuper()) { + Instruction::LoadSuperConstructor super; + bytecodeGenerator->addInstruction(super); + constructor = Reference::fromAccumulator(this).storeOnStack(); + } else { + constructor = base.storeOnStack(); + } + + auto calldata = pushArgs(arguments); + if (hasError) + return; + + if (base.isSuper()) + Reference::fromStackSlot(this, CallData::NewTarget).loadInAccumulator(); + else + constructor.loadInAccumulator(); + + if (calldata.hasSpread) { + Instruction::ConstructWithSpread create; + create.func = constructor.stackSlot(); + create.argc = calldata.argc; + create.argv = calldata.argv; + bytecodeGenerator->addInstruction(create); + } else { + Instruction::Construct create; + create.func = constructor.stackSlot(); + create.argc = calldata.argc; + create.argv = calldata.argv; + bytecodeGenerator->addInstruction(create); + } + if (base.isSuper()) + // set the result up as the thisObject + Reference::fromAccumulator(this).storeOnStack(CallData::This); + + _expr.setResult(Reference::fromAccumulator(this)); +} + bool Codegen::visit(NewExpression *ast) { if (hasError) return false; - Result base = expression(ast->expression); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); + + Reference base = expression(ast->expression); if (hasError) return false; - IR::Expr *expr = *base; - if (expr && !expr->asTemp() && !expr->asArgLocal() && !expr->asName() && !expr->asMember()) { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), expr); - expr = _block->TEMP(t); + if (base.isSuper()) { + throwSyntaxError(ast->expression->firstSourceLocation(), QStringLiteral("Cannot use new with super.")); + return false; } - _expr.code = _block->NEW(expr, 0); + + handleConstruct(base, nullptr); return false; } @@ -1602,29 +2491,18 @@ bool Codegen::visit(NewMemberExpression *ast) if (hasError) return false; - Result base = expression(ast->base); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); + + Reference base = expression(ast->base); if (hasError) return false; - IR::Expr *expr = *base; - if (expr && !expr->asTemp() && !expr->asArgLocal() && !expr->asName() && !expr->asMember()) { - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), expr); - expr = _block->TEMP(t); + if (base.isSuper()) { + throwSyntaxError(ast->base->firstSourceLocation(), QStringLiteral("Cannot use new with super.")); + return false; } - IR::ExprList *args = 0, **args_it = &args; - for (ArgumentList *it = ast->arguments; it; it = it->next) { - Result arg = expression(it->expression); - if (hasError) - return false; - IR::Expr *actual = argument(*arg); - *args_it = _function->New<IR::ExprList>(); - (*args_it)->init(actual); - args_it = &(*args_it)->next; - } - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), _block->NEW(expr, args)); - _expr.code = _block->TEMP(t); + handleConstruct(base, ast->arguments); return false; } @@ -1633,12 +2511,8 @@ bool Codegen::visit(NotExpression *ast) if (hasError) return false; - Result expr = expression(ast->expression); - if (hasError) - return false; - const unsigned r = _block->newTemp(); - setLocation(move(_block->TEMP(r), unop(IR::OpNot, *expr, ast->notToken)), ast->notToken); - _expr.code = _block->TEMP(r); + TailCallBlocker blockTailCalls(this); + _expr.setResult(unop(Not, expression(ast->expression))); return false; } @@ -1647,8 +2521,10 @@ bool Codegen::visit(NullExpression *) if (hasError) return false; - if (_expr.accept(cx)) _block->JUMP(_expr.iffalse); - else _expr.code = _block->CONST(IR::NullType, 0); + if (_expr.accept(cx)) + bytecodeGenerator->jump().link(*_expr.iffalse()); + else + _expr.setResult(Reference::fromConst(this, Encode::null())); return false; } @@ -1658,164 +2534,120 @@ bool Codegen::visit(NumericLiteral *ast) if (hasError) return false; - if (_expr.accept(cx)) { - if (ast->value) _block->JUMP(_expr.iftrue); - else _block->JUMP(_expr.iffalse); - } else { - _expr.code = _block->CONST(IR::NumberType, ast->value); - } + _expr.setResult(Reference::fromConst(this, QV4::Encode::smallestNumber(ast->value))); return false; } -struct ObjectPropertyValue { - ObjectPropertyValue() - : value(0) - , getter(-1) - , setter(-1) - {} - - IR::Expr *value; - int getter; // index in _module->functions or -1 if not set - int setter; - - bool hasGetter() const { return getter >= 0; } - bool hasSetter() const { return setter >= 0; } -}; - -bool Codegen::visit(ObjectLiteral *ast) +bool Codegen::visit(ObjectPattern *ast) { if (hasError) return false; - QMap<QString, ObjectPropertyValue> valueMap; - - for (PropertyAssignmentList *it = ast->properties; it; it = it->next) { - QString name = it->assignment->name->asString(); - if (PropertyNameAndValue *nv = AST::cast<AST::PropertyNameAndValue *>(it->assignment)) { - Result value = expression(nv->value); - if (hasError) - return false; - ObjectPropertyValue &v = valueMap[name]; - if (v.hasGetter() || v.hasSetter() || (_function->isStrict && v.value)) { - throwSyntaxError(nv->lastSourceLocation(), - QStringLiteral("Illegal duplicate key '%1' in object literal").arg(name)); - return false; - } + TailCallBlocker blockTailCalls(this); - valueMap[name].value = *value; - } else if (PropertyGetterSetter *gs = AST::cast<AST::PropertyGetterSetter *>(it->assignment)) { - const int function = defineFunction(name, gs, gs->formals, gs->functionBody ? gs->functionBody->elements : 0); - ObjectPropertyValue &v = valueMap[name]; - if (v.value || - (gs->type == PropertyGetterSetter::Getter && v.hasGetter()) || - (gs->type == PropertyGetterSetter::Setter && v.hasSetter())) { - throwSyntaxError(gs->lastSourceLocation(), - QStringLiteral("Illegal duplicate key '%1' in object literal").arg(name)); - return false; - } - if (gs->type == PropertyGetterSetter::Getter) - v.getter = function; - else - v.setter = function; - } else { - Q_UNREACHABLE(); - } - } + RegisterScope scope(this); - // The linked-list arguments to builtin_define_object_literal - // begin with a CONST counting the number of key/value pairs, followed by the - // key value pairs, followed by the array entries. - IR::ExprList *args = _function->New<IR::ExprList>(); + QStringList members; - IR::Const *entryCountParam = _function->New<IR::Const>(); - entryCountParam->init(IR::SInt32Type, 0); - args->expr = entryCountParam; - args->next = 0; + int argc = 0; + int args = 0; + auto push = [this, &args, &argc](const Reference &arg) { + int temp = bytecodeGenerator->newRegister(); + if (argc == 0) + args = temp; + (void) arg.storeOnStack(temp); + ++argc; + }; - IR::ExprList *keyValueEntries = 0; - IR::ExprList *currentKeyValueEntry = 0; - int keyValueEntryCount = 0; - IR::ExprList *arrayEntries = 0; + PatternPropertyList *it = ast->properties; + for (; it; it = it->next) { + PatternProperty *p = it->property; + AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(p->name); + if (cname || p->type != PatternProperty::Literal) + break; + QString name = p->name->asString(); + uint arrayIndex = QV4::String::toArrayIndex(name); + if (arrayIndex != UINT_MAX) + break; + if (members.contains(name)) + break; + members.append(name); - IR::ExprList *currentArrayEntry = 0; + { + RegisterScope innerScope(this); + Reference value = expression(p->initializer); + if (hasError) + return false; + value.loadInAccumulator(); + } + push(Reference::fromAccumulator(this)); + } - for (QMap<QString, ObjectPropertyValue>::iterator it = valueMap.begin(); it != valueMap.end(); ) { - IR::ExprList **currentPtr = 0; - uint keyAsIndex = QV4::String::toArrayIndex(it.key()); - if (keyAsIndex != UINT_MAX) { - if (!arrayEntries) { - arrayEntries = _function->New<IR::ExprList>(); - currentArrayEntry = arrayEntries; - } else { - currentArrayEntry->next = _function->New<IR::ExprList>(); - currentArrayEntry = currentArrayEntry->next; - } - currentPtr = ¤tArrayEntry; - IR::Const *idx = _function->New<IR::Const>(); - idx->init(IR::UInt32Type, keyAsIndex); - (*currentPtr)->expr = idx; + int classId = jsUnitGenerator->registerJSClass(members); + + // handle complex property setters + for (; it; it = it->next) { + PatternProperty *p = it->property; + AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(p->name); + ObjectLiteralArgument argType = ObjectLiteralArgument::Value; + if (p->type == PatternProperty::Method) + argType = ObjectLiteralArgument::Method; + else if (p->type == PatternProperty::Getter) + argType = ObjectLiteralArgument::Getter; + else if (p->type == PatternProperty::Setter) + argType = ObjectLiteralArgument::Setter; + + Reference::fromConst(this, Encode(int(argType))).loadInAccumulator(); + push(Reference::fromAccumulator(this)); + + if (cname) { + RegisterScope innerScope(this); + Reference name = expression(cname->expression); + if (hasError) + return false; + name.loadInAccumulator(); } else { - if (!keyValueEntries) { - keyValueEntries = _function->New<IR::ExprList>(); - currentKeyValueEntry = keyValueEntries; - } else { - currentKeyValueEntry->next = _function->New<IR::ExprList>(); - currentKeyValueEntry = currentKeyValueEntry->next; + QString name = p->name->asString(); +#if 0 + uint arrayIndex = QV4::String::toArrayIndex(name); + if (arrayIndex != UINT_MAX) { + Reference::fromConst(this, Encode(arrayIndex)).loadInAccumulator(); + } else +#endif + { + Instruction::LoadRuntimeString instr; + instr.stringId = registerString(name); + bytecodeGenerator->addInstruction(instr); } - currentPtr = ¤tKeyValueEntry; - (*currentPtr)->expr = _block->NAME(it.key(), 0, 0); - keyValueEntryCount++; } - - IR::ExprList *¤t = *currentPtr; - if (it->value) { - current->next = _function->New<IR::ExprList>(); - current = current->next; - current->expr = _block->CONST(IR::BoolType, true); - - unsigned value = _block->newTemp(); - move(_block->TEMP(value), it->value); - - current->next = _function->New<IR::ExprList>(); - current = current->next; - current->expr = _block->TEMP(value); - } else { - current->next = _function->New<IR::ExprList>(); - current = current->next; - current->expr = _block->CONST(IR::BoolType, false); - - unsigned getter = _block->newTemp(); - unsigned setter = _block->newTemp(); - move(_block->TEMP(getter), it->hasGetter() ? _block->CLOSURE(it->getter) : _block->CONST(IR::UndefinedType, 0)); - move(_block->TEMP(setter), it->hasSetter() ? _block->CLOSURE(it->setter) : _block->CONST(IR::UndefinedType, 0)); - - current->next = _function->New<IR::ExprList>(); - current = current->next; - current->expr = _block->TEMP(getter); - current->next = _function->New<IR::ExprList>(); - current = current->next; - current->expr = _block->TEMP(setter); + push(Reference::fromAccumulator(this)); + { + RegisterScope innerScope(this); + if (p->type != PatternProperty::Literal) { + // need to get the closure id for the method + FunctionExpression *f = p->initializer->asFunctionDefinition(); + Q_ASSERT(f); + int function = defineFunction(f->name.toString(), f, f->formals, f->body); + if (hasError) + return false; + Reference::fromConst(this, Encode(function)).loadInAccumulator(); + } else { + Reference value = expression(p->initializer); + if (hasError) + return false; + value.loadInAccumulator(); + } } - - it = valueMap.erase(it); - } - - entryCountParam->value = keyValueEntryCount; - - if (keyValueEntries) - args->next = keyValueEntries; - if (arrayEntries) { - if (currentKeyValueEntry) - currentKeyValueEntry->next = arrayEntries; - else - args->next = arrayEntries; + push(Reference::fromAccumulator(this)); } - const unsigned t = _block->newTemp(); - move(_block->TEMP(t), _block->CALL(_block->NAME(IR::Name::builtin_define_object_literal, - ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn), args)); - - _expr.code = _block->TEMP(t); + Instruction::DefineObjectLiteral call; + call.internalClassId = classId; + call.argc = argc; + call.args = Moth::StackSlot::createRegister(args); + bytecodeGenerator->addInstruction(call); + Reference result = Reference::fromAccumulator(this); + _expr.setResult(result); return false; } @@ -1824,25 +2656,17 @@ bool Codegen::visit(PostDecrementExpression *ast) if (hasError) return false; - Result expr = expression(ast->base); + Reference expr = expression(ast->base); if (hasError) return false; - if (!expr->isLValue()) { + if (!expr.isLValue()) { throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation")); return false; } - if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->decrementToken)) + if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->decrementToken)) return false; - const unsigned oldValue = _block->newTemp(); - setLocation(move(_block->TEMP(oldValue), unop(IR::OpUPlus, *expr, ast->decrementToken)), ast->decrementToken); - - const unsigned newValue = _block->newTemp(); - setLocation(move(_block->TEMP(newValue), binop(IR::OpSub, _block->TEMP(oldValue), _block->CONST(IR::NumberType, 1), ast->decrementToken)), ast->decrementToken); - setLocation(move(*expr, _block->TEMP(newValue)), ast->decrementToken); - - if (!_expr.accept(nx)) - _expr.code = _block->TEMP(oldValue); + _expr.setResult(unop(PostDecrement, expr)); return false; } @@ -1852,53 +2676,35 @@ bool Codegen::visit(PostIncrementExpression *ast) if (hasError) return false; - Result expr = expression(ast->base); + Reference expr = expression(ast->base); if (hasError) return false; - if (!expr->isLValue()) { + if (!expr.isLValue()) { throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation")); return false; } - if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->incrementToken)) + if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->incrementToken)) return false; - const unsigned oldValue = _block->newTemp(); - setLocation(move(_block->TEMP(oldValue), unop(IR::OpUPlus, *expr, ast->incrementToken)), ast->incrementToken); - - const unsigned newValue = _block->newTemp(); - setLocation(move(_block->TEMP(newValue), binop(IR::OpAdd, _block->TEMP(oldValue), _block->CONST(IR::NumberType, 1), ast->incrementToken)), ast->incrementToken); - setLocation(move(*expr, _block->TEMP(newValue)), ast->incrementToken); - - if (!_expr.accept(nx)) - _expr.code = _block->TEMP(oldValue); - + _expr.setResult(unop(PostIncrement, expr)); return false; } bool Codegen::visit(PreDecrementExpression *ast) -{ - if (hasError) +{ if (hasError) return false; - Result expr = expression(ast->expression); + Reference expr = expression(ast->expression); if (hasError) return false; - if (!expr->isLValue()) { + if (!expr.isLValue()) { throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix ++ operator applied to value that is not a reference.")); return false; } - if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->decrementToken)) + if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->decrementToken)) return false; - IR::Expr *op = binop(IR::OpSub, *expr, _block->CONST(IR::NumberType, 1), ast->decrementToken); - if (_expr.accept(nx)) { - setLocation(move(*expr, op), ast->decrementToken); - } else { - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), op), ast->decrementToken); - setLocation(move(*expr, _block->TEMP(t)), ast->decrementToken); - _expr.code = _block->TEMP(t); - } + _expr.setResult(unop(PreDecrement, expr)); return false; } @@ -1907,25 +2713,17 @@ bool Codegen::visit(PreIncrementExpression *ast) if (hasError) return false; - Result expr = expression(ast->expression); + Reference expr = expression(ast->expression); if (hasError) return false; - if (!expr->isLValue()) { + if (!expr.isLValue()) { throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix ++ operator applied to value that is not a reference.")); return false; } - if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->incrementToken)) + if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->incrementToken)) return false; - IR::Expr *op = binop(IR::OpAdd, unop(IR::OpUPlus, *expr, ast->incrementToken), _block->CONST(IR::NumberType, 1), ast->incrementToken); - if (_expr.accept(nx)) { - setLocation(move(*expr, op), ast->incrementToken); - } else { - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), op), ast->incrementToken); - setLocation(move(*expr, _block->TEMP(t)), ast->incrementToken); - _expr.code = _block->TEMP(t); - } + _expr.setResult(unop(PreIncrement, expr)); return false; } @@ -1934,7 +2732,14 @@ bool Codegen::visit(RegExpLiteral *ast) if (hasError) return false; - _expr.code = _block->REGEXP(_function->newString(ast->pattern.toString()), ast->flags); + auto r = Reference::fromStackSlot(this); + r.isReadonly = true; + _expr.setResult(r); + + Instruction::MoveRegExp instr; + instr.regExpId = jsUnitGenerator->registerRegExp(ast); + instr.destReg = r.stackSlot(); + bytecodeGenerator->addInstruction(instr); return false; } @@ -1943,30 +2748,85 @@ bool Codegen::visit(StringLiteral *ast) if (hasError) return false; - _expr.code = _block->STRING(_function->newString(ast->value.toString())); + auto r = Reference::fromAccumulator(this); + r.isReadonly = true; + _expr.setResult(r); + + Instruction::LoadRuntimeString instr; + instr.stringId = registerString(ast->value.toString()); + bytecodeGenerator->addInstruction(instr); return false; } -bool Codegen::visit(ThisExpression *ast) +bool Codegen::visit(TemplateLiteral *ast) { if (hasError) return false; - _expr.code = _block->NAME(QStringLiteral("this"), ast->thisToken.startLine, ast->thisToken.startColumn); + TailCallBlocker blockTailCalls(this); + + Instruction::LoadRuntimeString instr; + instr.stringId = registerString(ast->value.toString()); + bytecodeGenerator->addInstruction(instr); + + if (ast->expression) { + RegisterScope scope(this); + int temp = bytecodeGenerator->newRegister(); + Instruction::StoreReg store; + store.reg = temp; + bytecodeGenerator->addInstruction(store); + + Reference expr = expression(ast->expression); + if (hasError) + return false; + + if (ast->next) { + int temp2 = bytecodeGenerator->newRegister(); + expr.storeOnStack(temp2); + visit(ast->next); + + Instruction::Add instr; + instr.lhs = temp2; + bytecodeGenerator->addTracingInstruction(instr); + } else { + expr.loadInAccumulator(); + } + + Instruction::Add instr; + instr.lhs = temp; + bytecodeGenerator->addTracingInstruction(instr); + } + + auto r = Reference::fromAccumulator(this); + r.isReadonly = true; + + _expr.setResult(r); return false; + } -bool Codegen::visit(TildeExpression *ast) +bool Codegen::visit(ThisExpression *) { if (hasError) return false; - Result expr = expression(ast->expression); + if (_context->isArrowFunction) { + Reference r = referenceForName(QStringLiteral("this"), false); + r.isReadonly = true; + _expr.setResult(r); + return false; + } + _expr.setResult(Reference::fromThis(this)); + return false; +} + +bool Codegen::visit(TildeExpression *ast) +{ if (hasError) return false; - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), unop(IR::OpCompl, *expr, ast->tildeToken)), ast->tildeToken); - _expr.code = _block->TEMP(t); + + TailCallBlocker blockTailCalls(this); + _expr.setResult(unop(Compl, expression(ast->expression))); return false; } @@ -1975,11 +2835,7 @@ bool Codegen::visit(TrueLiteral *) if (hasError) return false; - if (_expr.accept(cx)) { - _block->JUMP(_expr.iftrue); - } else { - _expr.code = _block->CONST(IR::BoolType, 1); - } + _expr.setResult(Reference::fromConst(this, QV4::Encode(true))); return false; } @@ -1988,12 +2844,25 @@ bool Codegen::visit(TypeOfExpression *ast) if (hasError) return false; - Result expr = expression(ast->expression); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); + + Reference expr = expression(ast->expression); if (hasError) return false; - IR::ExprList *args = _function->New<IR::ExprList>(); - args->init(reference(*expr)); - _expr.code = call(_block->NAME(IR::Name::builtin_typeof, ast->typeofToken.startLine, ast->typeofToken.startColumn), args); + + if (expr.type == Reference::Name) { + // special handling as typeof doesn't throw here + Instruction::TypeofName instr; + instr.name = expr.nameAsIndex(); + bytecodeGenerator->addInstruction(instr); + } else { + expr.loadInAccumulator(); + Instruction::TypeofValue instr; + bytecodeGenerator->addInstruction(instr); + } + _expr.setResult(Reference::fromAccumulator(this)); + return false; } @@ -2002,12 +2871,8 @@ bool Codegen::visit(UnaryMinusExpression *ast) if (hasError) return false; - Result expr = expression(ast->expression); - if (hasError) - return false; - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), unop(IR::OpUMinus, *expr, ast->minusToken)), ast->minusToken); - _expr.code = _block->TEMP(t); + TailCallBlocker blockTailCalls(this); + _expr.setResult(unop(UMinus, expression(ast->expression))); return false; } @@ -2016,12 +2881,8 @@ bool Codegen::visit(UnaryPlusExpression *ast) if (hasError) return false; - Result expr = expression(ast->expression); - if (hasError) - return false; - const unsigned t = _block->newTemp(); - setLocation(move(_block->TEMP(t), unop(IR::OpUPlus, *expr, ast->plusToken)), ast->plusToken); - _expr.code = _block->TEMP(t); + TailCallBlocker blockTailCalls(this); + _expr.setResult(unop(UPlus, expression(ast->expression))); return false; } @@ -2030,8 +2891,11 @@ bool Codegen::visit(VoidExpression *ast) if (hasError) return false; + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); + statement(ast->expression); - _expr.code = _block->CONST(IR::UndefinedType, 0); + _expr.setResult(Reference::fromConst(this, Encode::undefined())); return false; } @@ -2040,162 +2904,265 @@ bool Codegen::visit(FunctionDeclaration * ast) if (hasError) return false; - if (_variableEnvironment->compilationMode == QmlBinding) - move(_block->TEMP(_returnAddress), _block->NAME(ast->name.toString(), 0, 0)); + // no need to block tail calls: the function body isn't visited here. + RegisterScope scope(this); + + if (_functionContext->contextType == ContextType::Binding) + referenceForName(ast->name.toString(), true).loadInAccumulator(); _expr.accept(nx); return false; } -int Codegen::defineFunction(const QString &name, AST::Node *ast, - AST::FormalParameterList *formals, - AST::SourceElements *body, - const QStringList &inheritedLocals) -{ - Loop *loop = 0; - qSwap(_loop, loop); - QStack<IR::BasicBlock *> exceptionHandlers; - qSwap(_exceptionHandlers, exceptionHandlers); - - ScopeAndFinally *scopeAndFinally = 0; - - enterEnvironment(ast); - IR::Function *function = _module->newFunction(name, _function); - int functionIndex = _module->functions.count() - 1; - - IR::BasicBlock *entryBlock = function->newBasicBlock(0); - IR::BasicBlock *exitBlock = function->newBasicBlock(0, IR::Function::DontInsertBlock); - function->hasDirectEval = _variableEnvironment->hasDirectEval || _variableEnvironment->compilationMode == EvalCode - || _module->debugMode; // Conditional breakpoints are like eval in the function - function->usesArgumentsObject = _variableEnvironment->parent && (_variableEnvironment->usesArgumentsObject == Environment::ArgumentsObjectUsed); - function->usesThis = _variableEnvironment->usesThis; - function->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, (int)QV4::Global::ReservedArgumentCount); - function->isStrict = _variableEnvironment->isStrict; - function->isNamedExpression = _variableEnvironment->isNamedFunctionExpression; - function->isQmlBinding = _variableEnvironment->compilationMode == QmlBinding; - - AST::SourceLocation loc = ast->firstSourceLocation(); - function->line = loc.startLine; - function->column = loc.startColumn; - - if (function->usesArgumentsObject) - _variableEnvironment->enter(QStringLiteral("arguments"), Environment::VariableDeclaration, AST::VariableDeclaration::FunctionScope); - - // variables in global code are properties of the global context object, not locals as with other functions. - if (_variableEnvironment->compilationMode == FunctionCode || _variableEnvironment->compilationMode == QmlBinding) { - unsigned t = 0; - for (Environment::MemberMap::iterator it = _variableEnvironment->members.begin(), end = _variableEnvironment->members.end(); it != end; ++it) { - const QString &local = it.key(); - function->LOCAL(local); - (*it).index = t; - entryBlock->MOVE(entryBlock->LOCAL(t, 0), entryBlock->CONST(IR::UndefinedType, 0)); - ++t; - } - } else { - if (!_variableEnvironment->isStrict) { - for (const QString &inheritedLocal : qAsConst(inheritedLocals)) { - function->LOCAL(inheritedLocal); - unsigned tempIndex = entryBlock->newTemp(); - Environment::Member member = { Environment::UndefinedMember, - static_cast<int>(tempIndex), 0, - AST::VariableDeclaration::VariableScope::FunctionScope }; - _variableEnvironment->members.insert(inheritedLocal, member); - } - } +bool Codegen::visit(YieldExpression *ast) +{ + if (inFormalParameterList) { + throwSyntaxError(ast->firstSourceLocation(), QLatin1String("yield is not allowed inside parameter lists")); + return false; + } - IR::ExprList *args = 0; - for (Environment::MemberMap::const_iterator it = _variableEnvironment->members.constBegin(), cend = _variableEnvironment->members.constEnd(); it != cend; ++it) { - const QString &local = it.key(); - IR::ExprList *next = function->New<IR::ExprList>(); - next->expr = entryBlock->NAME(local, 0, 0); - next->next = args; - args = next; - } - if (args) { - IR::ExprList *next = function->New<IR::ExprList>(); - next->expr = entryBlock->CONST(IR::BoolType, false); // ### Investigate removal of bool deletable - next->next = args; - args = next; + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); + Reference expr = ast->expression ? expression(ast->expression) : Reference::fromConst(this, Encode::undefined()); + if (hasError) + return false; - entryBlock->EXP(entryBlock->CALL(entryBlock->NAME(IR::Name::builtin_declare_vars, 0, 0), args)); - } + Reference acc = Reference::fromAccumulator(this); + + if (ast->isYieldStar) { + Reference iterator = Reference::fromStackSlot(this); + Reference lhsValue = Reference::fromConst(this, Encode::undefined()).storeOnStack(); + + expr.loadInAccumulator(); + Instruction::GetIterator getIterator; + getIterator.iterator = static_cast<int>(AST::ForEachType::Of); + bytecodeGenerator->addInstruction(getIterator); + iterator.storeConsumeAccumulator(); + Instruction::LoadUndefined load; + bytecodeGenerator->addInstruction(load); + + BytecodeGenerator::Label in = bytecodeGenerator->newLabel(); + bytecodeGenerator->jump().link(in); + + BytecodeGenerator::Label loop = bytecodeGenerator->label(); + + lhsValue.loadInAccumulator(); + Instruction::YieldStar yield; + bytecodeGenerator->addInstruction(yield); + + in.link(); + + Instruction::IteratorNextForYieldStar next; + next.object = lhsValue.stackSlot(); + next.iterator = iterator.stackSlot(); + bytecodeGenerator->addInstruction(next); + + BytecodeGenerator::Jump done = bytecodeGenerator->jumpTrue(); + bytecodeGenerator->jumpNotUndefined().link(loop); + lhsValue.loadInAccumulator(); + emitReturn(acc); + + + done.link(); + + lhsValue.loadInAccumulator(); + _expr.setResult(acc); + return false; } - unsigned returnAddress = entryBlock->newTemp(); + expr.loadInAccumulator(); + Instruction::Yield yield; + bytecodeGenerator->addInstruction(yield); + Instruction::Resume resume; + BytecodeGenerator::Jump jump = bytecodeGenerator->addJumpInstruction(resume); + emitReturn(acc); + jump.link(); + _expr.setResult(acc); + return false; +} + +static bool endsWithReturn(Module *module, Node *node) +{ + if (!node) + return false; + if (AST::cast<ReturnStatement *>(node)) + return true; + if (AST::cast<ThrowStatement *>(node)) + return true; + if (Program *p = AST::cast<Program *>(node)) + return endsWithReturn(module, p->statements); + if (StatementList *sl = AST::cast<StatementList *>(node)) { + while (sl->next) + sl = sl->next; + return endsWithReturn(module, sl->statement); + } + if (Block *b = AST::cast<Block *>(node)) { + Context *blockContext = module->contextMap.value(node); + if (blockContext->requiresExecutionContext) + // we need to emit a return statement here, because of the + // unwind handler + return false; + return endsWithReturn(module, b->statements); + } + if (IfStatement *is = AST::cast<IfStatement *>(node)) + return is->ko && endsWithReturn(module, is->ok) && endsWithReturn(module, is->ko); + return false; +} + +int Codegen::defineFunction(const QString &name, AST::Node *ast, + AST::FormalParameterList *formals, + AST::StatementList *body) +{ + enterContext(ast); + + if (_context->functionIndex >= 0) + // already defined + return leaveContext(); + + _context->name = name; + _module->functions.append(_context); + _context->functionIndex = _module->functions.count() - 1; - entryBlock->MOVE(entryBlock->TEMP(returnAddress), entryBlock->CONST(IR::UndefinedType, 0)); - setLocation(exitBlock->RET(exitBlock->TEMP(returnAddress)), ast->lastSourceLocation()); + Context *savedFunctionContext = _functionContext; + _functionContext = _context; + ControlFlow *savedControlFlow = controlFlow; + controlFlow = nullptr; - qSwap(_function, function); - qSwap(_block, entryBlock); - qSwap(_exitBlock, exitBlock); + if (_context->contextType == ContextType::Global || _context->contextType == ContextType::ScriptImportedByQML) { + _module->blocks.append(_context); + _context->blockIndex = _module->blocks.count() - 1; + } + if (_module->debugMode) // allow the debugger to see overwritten arguments + _context->argumentsCanEscape = true; + + // When a user writes the following QML signal binding: + // onSignal: function() { doSomethingUsefull } + // we will generate a binding function that just returns the closure. However, that's not useful + // at all, because if the onSignal is a signal handler, the user is actually making it explicit + // that the binding is a function, so we should execute that. However, we don't know that during + // AOT compilation, so mark the surrounding function as only-returning-a-closure. + _context->returnsClosure = body && body->statement && cast<ExpressionStatement *>(body->statement) && cast<FunctionExpression *>(cast<ExpressionStatement *>(body->statement)->expression); + + BytecodeGenerator bytecode(_context->line, _module->debugMode); + BytecodeGenerator *savedBytecodeGenerator; + savedBytecodeGenerator = bytecodeGenerator; + bytecodeGenerator = &bytecode; + bytecodeGenerator->setLocation(ast->firstSourceLocation()); + BytecodeGenerator::Label *savedReturnLabel = _returnLabel; + _returnLabel = nullptr; + + bool savedFunctionEndsWithReturn = functionEndsWithReturn; + functionEndsWithReturn = endsWithReturn(_module, body); + bytecodeGenerator->setTracing(_functionContext->canUseTracingJit(), _context->arguments.size()); + + // reserve the js stack frame (Context & js Function & accumulator) + bytecodeGenerator->newRegisterArray(sizeof(CallData)/sizeof(Value) - 1 + _context->arguments.size()); + + bool _inFormalParameterList = false; + qSwap(_inFormalParameterList, inFormalParameterList); + + int returnAddress = -1; + bool _requiresReturnValue = _context->requiresImplicitReturnValue(); + qSwap(requiresReturnValue, _requiresReturnValue); + returnAddress = bytecodeGenerator->newRegister(); qSwap(_returnAddress, returnAddress); - qSwap(_scopeAndFinally, scopeAndFinally); - for (FormalParameterList *it = formals; it; it = it->next) { - _function->RECEIVE(it->name.toString()); + // register the lexical scope for global code + if (!_context->parent && _context->requiresExecutionContext) { + _module->blocks.append(_context); + _context->blockIndex = _module->blocks.count() - 1; } - for (const Environment::Member &member : qAsConst(_variableEnvironment->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); - if (! _variableEnvironment->parent) { - move(_block->NAME(member.function->name.toString(), member.function->identifierToken.startLine, member.function->identifierToken.startColumn), - _block->CLOSURE(function)); + TailCallBlocker maybeBlockTailCalls(this, _context->canHaveTailCalls()); + + RegisterScope registerScope(this); + _context->emitBlockHeader(this); + + { + QScopedValueRollback<bool> inFormals(inFormalParameterList, true); + TailCallBlocker blockTailCalls(this); // we're not in the FunctionBody or ConciseBody yet + + int argc = 0; + while (formals) { + PatternElement *e = formals->element; + if (!e) { + if (!formals->next) + // trailing comma + break; + Q_UNREACHABLE(); + } + + Reference arg = referenceForName(e->bindingIdentifier.toString(), true); + if (e->type == PatternElement::RestElement) { + Q_ASSERT(!formals->next); + Instruction::CreateRestParameter rest; + rest.argIndex = argc; + bytecodeGenerator->addInstruction(rest); + arg.storeConsumeAccumulator(); } else { - Q_ASSERT(member.index >= 0); - move(_block->LOCAL(member.index, 0), _block->CLOSURE(function)); + if (e->bindingTarget || e->initializer) { + initializeAndDestructureBindingElement(e, arg); + if (hasError) + break; + } } + formals = formals->next; + ++argc; } } - if (_function->usesArgumentsObject) { - move(identifier(QStringLiteral("arguments"), ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn), - _block->CALL(_block->NAME(IR::Name::builtin_setup_argument_object, - ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn), 0)); - } - if (_function->usesThis && !_function->isStrict) { - // make sure we convert this to an object - _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_convert_this_to_object, - ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn), 0)); + + if (_context->isGenerator) { + Instruction::Yield yield; + bytecodeGenerator->addInstruction(yield); } beginFunctionBodyHook(); - sourceElements(body); + statementList(body); - _function->addBasicBlock(_exitBlock); - - _block->JUMP(_exitBlock); - - qSwap(_function, function); - qSwap(_block, entryBlock); - qSwap(_exitBlock, exitBlock); - qSwap(_returnAddress, returnAddress); - qSwap(_scopeAndFinally, scopeAndFinally); - qSwap(_exceptionHandlers, exceptionHandlers); - qSwap(_loop, loop); + if (!hasError) { + bytecodeGenerator->setLocation(ast->lastSourceLocation()); + _context->emitBlockFooter(this); - leaveEnvironment(); + if (_returnLabel || !functionEndsWithReturn) { + if (_returnLabel) + _returnLabel->link(); - return functionIndex; -} + if (_returnLabel || requiresReturnValue) { + Instruction::LoadReg load; + load.reg = Moth::StackSlot::createRegister(_returnAddress); + bytecodeGenerator->addInstruction(load); + } else { + Reference::fromConst(this, Encode::undefined()).loadInAccumulator(); + } -bool Codegen::visit(FunctionSourceElement *ast) -{ - if (hasError) - return false; + bytecodeGenerator->addInstruction(Instruction::Ret()); + } - statement(ast->declaration); - return false; -} + Q_ASSERT(_context == _functionContext); + bytecodeGenerator->finalize(_context); + _context->registerCountInFunction = bytecodeGenerator->registerCount(); + _context->nTraceInfos = bytecodeGenerator->traceInfoCount(); + static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); + if (showCode) { + qDebug() << "=== Bytecode for" << _context->name << "strict mode" << _context->isStrict + << "register count" << _context->registerCountInFunction << "implicit return" << requiresReturnValue; + QV4::Moth::dumpBytecode(_context->code, _context->locals.size(), _context->arguments.size(), + _context->line, _context->lineNumberMapping); + qDebug(); + } + } -bool Codegen::visit(StatementSourceElement *ast) -{ - if (hasError) - return false; + qSwap(_returnAddress, returnAddress); + qSwap(requiresReturnValue, _requiresReturnValue); + qSwap(_inFormalParameterList, inFormalParameterList); + bytecodeGenerator = savedBytecodeGenerator; + delete _returnLabel; + _returnLabel = savedReturnLabel; + controlFlow = savedControlFlow; + functionEndsWithReturn = savedFunctionEndsWithReturn; + _functionContext = savedFunctionContext; - statement(ast->statement); - return false; + return leaveContext(); } bool Codegen::visit(Block *ast) @@ -2203,9 +3170,10 @@ bool Codegen::visit(Block *ast) if (hasError) return false; - for (StatementList *it = ast->statements; it; it = it->next) { - statement(it->statement); - } + RegisterScope scope(this); + + ControlFlowBlock controlFlow(this, ast); + statementList(ast->statements); return false; } @@ -2214,25 +3182,23 @@ bool Codegen::visit(BreakStatement *ast) if (hasError) return false; - if (!_loop) { + // no need to block tail calls here: children aren't visited + if (!controlFlow) { throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Break outside of loop")); return false; } - Loop *loop = 0; - if (ast->label.isEmpty()) - loop = _loop; - else { - for (loop = _loop; loop; loop = loop->parent) { - if (loop->labelledStatement && loop->labelledStatement->label == ast->label) - break; - } - if (!loop) { + + ControlFlow::UnwindTarget target = controlFlow->unwindTarget(ControlFlow::Break, ast->label.toString()); + if (!target.linkLabel.isValid()) { + if (ast->label.isEmpty()) + throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Break outside of loop")); + else throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString())); - return false; - } + return false; } - unwindException(loop->scopeAndFinally); - _block->JUMP(loop->breakBlock); + + bytecodeGenerator->unwindToLabel(target.unwindLevel, target.linkLabel); + return false; } @@ -2241,31 +3207,25 @@ bool Codegen::visit(ContinueStatement *ast) if (hasError) return false; - Loop *loop = 0; - if (ast->label.isEmpty()) { - for (loop = _loop; loop; loop = loop->parent) { - if (loop->continueBlock) - break; - } - } else { - for (loop = _loop; loop; loop = loop->parent) { - if (loop->labelledStatement && loop->labelledStatement->label == ast->label) { - if (!loop->continueBlock) - loop = 0; - break; - } - } - if (!loop) { - throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString())); - return false; - } + // no need to block tail calls here: children aren't visited + RegisterScope scope(this); + + if (!controlFlow) { + throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Continue outside of loop")); + return false; } - if (!loop) { - throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("continue outside of loop")); + + ControlFlow::UnwindTarget target = controlFlow->unwindTarget(ControlFlow::Continue, ast->label.toString()); + if (!target.linkLabel.isValid()) { + if (ast->label.isEmpty()) + throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString())); + else + throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("continue outside of loop")); return false; } - unwindException(loop->scopeAndFinally); - _block->JUMP(loop->continueBlock); + + bytecodeGenerator->unwindToLabel(target.unwindLevel, target.linkLabel); + return false; } @@ -2278,47 +3238,59 @@ bool Codegen::visit(DebuggerStatement *) bool Codegen::visit(DoWhileStatement *ast) { if (hasError) - return true; + return false; - IR::BasicBlock *loopbody = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *loopcond = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *loopend = _function->newBasicBlock(exceptionHandler()); + RegisterScope scope(this); - enterLoop(ast, loopend, loopcond); + BytecodeGenerator::Label body = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label cond = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); - _block->JUMP(loopbody); + ControlFlowLoop flow(this, &end, &cond); - _block = loopbody; - statement(ast->statement); - setJumpOutLocation(_block->JUMP(loopcond), ast->statement, ast->semicolonToken); + // special case that is not a loop: + // do {...} while (false) + if (!AST::cast<FalseLiteral *>(ast->expression)) + bytecodeGenerator->addLoopStart(body); - _block = loopcond; - condition(ast->expression, loopbody, loopend); - - _block = loopend; + body.link(); + statement(ast->statement); + setJumpOutLocation(bytecodeGenerator, ast->statement, ast->semicolonToken); + + cond.link(); + if (AST::cast<TrueLiteral *>(ast->expression)) { + // do {} while (true) -> just jump back to the loop body, no need to generate a condition + bytecodeGenerator->jump().link(body); + } else if (AST::cast<FalseLiteral *>(ast->expression)) { + // do {} while (false) -> fall through, no need to generate a condition + } else { + TailCallBlocker blockTailCalls(this); + condition(ast->expression, &body, &end, false); + } - leaveLoop(); + end.link(); return false; } bool Codegen::visit(EmptyStatement *) { - if (hasError) - return true; - return false; } bool Codegen::visit(ExpressionStatement *ast) { if (hasError) - return true; + return false; - if (_variableEnvironment->compilationMode == EvalCode || _variableEnvironment->compilationMode == QmlBinding) { - Result e = expression(ast->expression); - if (*e) - move(_block->TEMP(_returnAddress), *e); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); + + if (requiresReturnValue) { + Reference e = expression(ast->expression); + if (hasError) + return false; + (void) e.storeOnStack(_returnAddress); } else { statement(ast->expression); } @@ -2328,81 +3300,138 @@ bool Codegen::visit(ExpressionStatement *ast) bool Codegen::visit(ForEachStatement *ast) { if (hasError) - return true; + return false; - IR::BasicBlock *foreachin = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *foreachbody = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *foreachend = _function->newBasicBlock(exceptionHandler()); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); - int objectToIterateOn = _block->newTemp(); - Result expr = expression(ast->expression); - if (hasError) - return false; - move(_block->TEMP(objectToIterateOn), *expr); - IR::ExprList *args = _function->New<IR::ExprList>(); - args->init(_block->TEMP(objectToIterateOn)); + Reference iterator = Reference::fromStackSlot(this); + Reference iteratorDone = Reference::fromConst(this, Encode(false)).storeOnStack(); + Reference lhsValue = Reference::fromStackSlot(this); - int iterator = _block->newTemp(); - move(_block->TEMP(iterator), _block->CALL(_block->NAME(IR::Name::builtin_foreach_iterator_object, 0, 0), args)); + // There should be a temporal block, so that variables declared in lhs shadow outside vars. + // This block should define a temporal dead zone for those variables. + { + RegisterScope innerScope(this); + ControlFlowBlock controlFlow(this, ast); + Reference expr = expression(ast->expression); + if (hasError) + return false; - enterLoop(ast, foreachend, foreachin); - _block->JUMP(foreachin); + expr.loadInAccumulator(); + Instruction::GetIterator iteratorObjInstr; + iteratorObjInstr.iterator = static_cast<int>(ast->type); + bytecodeGenerator->addInstruction(iteratorObjInstr); + iterator.storeConsumeAccumulator(); + } - _block = foreachbody; - int temp = _block->newTemp(); - Result init = expression(ast->initialiser); - if (hasError) - return false; - move(*init, _block->TEMP(temp)); - statement(ast->statement); - setJumpOutLocation(_block->JUMP(foreachin), ast->statement, ast->forToken); + BytecodeGenerator::Label in = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); + + { + auto cleanup = [ast, iterator, iteratorDone, this]() { + if (ast->type == ForEachType::Of) { + iterator.loadInAccumulator(); + Instruction::IteratorClose close; + close.done = iteratorDone.stackSlot(); + bytecodeGenerator->addInstruction(close); + } + }; + ControlFlowLoop flow(this, &end, &in, cleanup); + bytecodeGenerator->addLoopStart(in); + in.link(); + iterator.loadInAccumulator(); + Instruction::IteratorNext next; + next.value = lhsValue.stackSlot(); + next.done = iteratorDone.stackSlot(); + bytecodeGenerator->addInstruction(next); + bytecodeGenerator->addTracingJumpInstruction(Instruction::JumpTrue()).link(end); + + // each iteration gets it's own context, as per spec + { + RegisterScope innerScope(this); + ControlFlowBlock controlFlow(this, ast); + + if (ExpressionNode *e = ast->lhs->expressionCast()) { + if (AST::Pattern *p = e->patternCast()) { + RegisterScope scope(this); + destructurePattern(p, lhsValue); + } else { + Reference lhs = expression(e); + if (hasError) + goto error; + if (!lhs.isLValue()) { + throwReferenceError(e->firstSourceLocation(), QStringLiteral("Invalid left-hand side expression for 'in' expression")); + goto error; + } + lhs = lhs.asLValue(); + lhsValue.loadInAccumulator(); + lhs.storeConsumeAccumulator(); + } + } else if (PatternElement *p = AST::cast<PatternElement *>(ast->lhs)) { + initializeAndDestructureBindingElement(p, lhsValue, /*isDefinition =*/ true); + if (hasError) + goto error; + } else { + Q_UNREACHABLE(); + } + + blockTailCalls.unblock(); + statement(ast->statement); + setJumpOutLocation(bytecodeGenerator, ast->statement, ast->forToken); + } + + bytecodeGenerator->jump().link(in); - _block = foreachin; + error: + end.link(); - args = _function->New<IR::ExprList>(); - args->init(_block->TEMP(iterator)); - move(_block->TEMP(temp), _block->CALL(_block->NAME(IR::Name::builtin_foreach_next_property_name, 0, 0), args)); - int null = _block->newTemp(); - move(_block->TEMP(null), _block->CONST(IR::NullType, 0)); - setLocation(cjump(_block->BINOP(IR::OpStrictNotEqual, _block->TEMP(temp), _block->TEMP(null)), foreachbody, foreachend), ast->forToken); - _block = foreachend; + // all execution paths need to end up here (normal loop exit, break, and exceptions) in + // order to reset the unwind handler, and to close the iterator in calse of an for-of loop. + } - leaveLoop(); return false; } bool Codegen::visit(ForStatement *ast) { if (hasError) - return true; + return false; - IR::BasicBlock *forcond = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *forbody = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *forstep = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *forend = _function->newBasicBlock(exceptionHandler()); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); - statement(ast->initialiser); - _block->JUMP(forcond); + ControlFlowBlock controlFlow(this, ast); - enterLoop(ast, forend, forstep); + if (ast->initialiser) + statement(ast->initialiser); + else if (ast->declarations) + variableDeclarationList(ast->declarations); - _block = forcond; - if (ast->condition) - condition(ast->condition, forbody, forend); - else - _block->JUMP(forbody); + BytecodeGenerator::Label cond = bytecodeGenerator->label(); + BytecodeGenerator::Label body = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label step = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); - _block = forbody; + ControlFlowLoop flow(this, &end, &step); + bytecodeGenerator->addLoopStart(cond); + condition(ast->condition, &body, &end, true); + + body.link(); + blockTailCalls.unblock(); statement(ast->statement); - setJumpOutLocation(_block->JUMP(forstep), ast->statement, ast->forToken); + blockTailCalls.reblock(); + setJumpOutLocation(bytecodeGenerator, ast->statement, ast->forToken); - _block = forstep; + step.link(); + if (_context->requiresExecutionContext) { + Instruction::CloneBlockContext clone; + bytecodeGenerator->addInstruction(clone); + } statement(ast->expression); - _block->JUMP(forcond); - - _block = forend; + bytecodeGenerator->jump().link(cond); - leaveLoop(); + end.link(); return false; } @@ -2410,38 +3439,46 @@ bool Codegen::visit(ForStatement *ast) bool Codegen::visit(IfStatement *ast) { if (hasError) - return true; + return false; - IR::BasicBlock *iftrue = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *iffalse = ast->ko ? _function->newBasicBlock(exceptionHandler()) : 0; - IR::BasicBlock *endif = _function->newBasicBlock(exceptionHandler()); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); - condition(ast->expression, iftrue, ast->ko ? iffalse : endif); + BytecodeGenerator::Label trueLabel = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label falseLabel = bytecodeGenerator->newLabel(); + condition(ast->expression, &trueLabel, &falseLabel, true); + blockTailCalls.unblock(); - _block = iftrue; + trueLabel.link(); statement(ast->ok); - setJumpOutLocation(_block->JUMP(endif), ast->ok, ast->ifToken); - if (ast->ko) { - _block = iffalse; - statement(ast->ko); - setJumpOutLocation(_block->JUMP(endif), ast->ko, ast->elseToken); + if (endsWithReturn(_module, ast)) { + falseLabel.link(); + statement(ast->ko); + } else { + BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump(); + falseLabel.link(); + statement(ast->ko); + jump_endif.link(); + } + } else { + falseLabel.link(); } - _block = endif; - return false; } bool Codegen::visit(LabelledStatement *ast) { if (hasError) - return true; + return false; + + RegisterScope scope(this); // check that no outer loop contains the label - Loop *l = _loop; + ControlFlow *l = controlFlow; while (l) { - if (l->labelledStatement && l->labelledStatement->label == ast->label) { + if (l->label() == ast->label) { QString error = QString(QStringLiteral("Label '%1' has already been declared")).arg(ast->label.toString()); throwSyntaxError(ast->firstSourceLocation(), error); return false; @@ -2454,364 +3491,205 @@ bool Codegen::visit(LabelledStatement *ast) AST::cast<AST::WhileStatement *>(ast->statement) || AST::cast<AST::DoWhileStatement *>(ast->statement) || AST::cast<AST::ForStatement *>(ast->statement) || - AST::cast<AST::ForEachStatement *>(ast->statement) || - AST::cast<AST::LocalForStatement *>(ast->statement) || - AST::cast<AST::LocalForEachStatement *>(ast->statement)) { + AST::cast<AST::ForEachStatement *>(ast->statement)) { statement(ast->statement); // labelledStatement will be associated with the ast->statement's loop. } else { - IR::BasicBlock *breakBlock = _function->newBasicBlock(exceptionHandler()); - enterLoop(ast->statement, breakBlock, /*continueBlock*/ 0); + BytecodeGenerator::Label breakLabel = bytecodeGenerator->newLabel(); + ControlFlowLoop flow(this, &breakLabel); statement(ast->statement); - _block->JUMP(breakBlock); - _block = breakBlock; - leaveLoop(); + breakLabel.link(); } return false; } -bool Codegen::visit(LocalForEachStatement *ast) -{ - if (hasError) - return true; - - IR::BasicBlock *foreachin = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *foreachbody = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *foreachend = _function->newBasicBlock(exceptionHandler()); - - variableDeclaration(ast->declaration); - - int iterator = _block->newTemp(); - move(_block->TEMP(iterator), *expression(ast->expression)); - IR::ExprList *args = _function->New<IR::ExprList>(); - args->init(_block->TEMP(iterator)); - move(_block->TEMP(iterator), _block->CALL(_block->NAME(IR::Name::builtin_foreach_iterator_object, 0, 0), args)); - - _block->JUMP(foreachin); - enterLoop(ast, foreachend, foreachin); - - _block = foreachbody; - int temp = _block->newTemp(); - move(identifier(ast->declaration->name.toString()), _block->TEMP(temp)); - statement(ast->statement); - setJumpOutLocation(_block->JUMP(foreachin), ast->statement, ast->forToken); - - _block = foreachin; - - args = _function->New<IR::ExprList>(); - args->init(_block->TEMP(iterator)); - move(_block->TEMP(temp), _block->CALL(_block->NAME(IR::Name::builtin_foreach_next_property_name, 0, 0), args)); - int null = _block->newTemp(); - move(_block->TEMP(null), _block->CONST(IR::NullType, 0)); - setLocation(cjump(_block->BINOP(IR::OpStrictNotEqual, _block->TEMP(temp), _block->TEMP(null)), foreachbody, foreachend), ast->forToken); - _block = foreachend; - - leaveLoop(); - return false; -} - -bool Codegen::visit(LocalForStatement *ast) +void Codegen::emitReturn(const Reference &expr) { - if (hasError) - return true; - - IR::BasicBlock *forcond = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *forbody = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *forstep = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *forend = _function->newBasicBlock(exceptionHandler()); - - variableDeclarationList(ast->declarations); - _block->JUMP(forcond); - - enterLoop(ast, forend, forstep); - - _block = forcond; - if (ast->condition) - condition(ast->condition, forbody, forend); - else - _block->JUMP(forbody); - - _block = forbody; - statement(ast->statement); - setJumpOutLocation(_block->JUMP(forstep), ast->statement, ast->forToken); - - _block = forstep; - statement(ast->expression); - _block->JUMP(forcond); - - _block = forend; - - leaveLoop(); - - return false; + ControlFlow::UnwindTarget target = controlFlow ? controlFlow->unwindTarget(ControlFlow::Return) : ControlFlow::UnwindTarget(); + if (target.linkLabel.isValid() && target.unwindLevel) { + Q_ASSERT(_returnAddress >= 0); + (void) expr.storeOnStack(_returnAddress); + bytecodeGenerator->unwindToLabel(target.unwindLevel, target.linkLabel); + } else { + expr.loadInAccumulator(); + bytecodeGenerator->addInstruction(Instruction::Ret()); + } } bool Codegen::visit(ReturnStatement *ast) { if (hasError) - return true; + return false; - if (_variableEnvironment->compilationMode != FunctionCode && _variableEnvironment->compilationMode != QmlBinding) { + if (_functionContext->contextType != ContextType::Function && _functionContext->contextType != ContextType::Binding) { throwSyntaxError(ast->returnToken, QStringLiteral("Return statement outside of function")); return false; } + Reference expr; if (ast->expression) { - Result expr = expression(ast->expression); - move(_block->TEMP(_returnAddress), *expr); + expr = expression(ast->expression); + if (hasError) + return false; + } else { + expr = Reference::fromConst(this, Encode::undefined()); } - // Since we're leaving, don't let any finally statements we emit as part of the unwinding - // jump to exception handlers at run-time if they throw. - IR::BasicBlock *unwindBlock = _function->newBasicBlock(/*no exception handler*/Q_NULLPTR); - _block->JUMP(unwindBlock); - _block = unwindBlock; + emitReturn(expr); - unwindException(0); - - _block->JUMP(_exitBlock); return false; } bool Codegen::visit(SwitchStatement *ast) { if (hasError) - return true; + return false; - IR::BasicBlock *switchend = _function->newBasicBlock(exceptionHandler()); + if (requiresReturnValue) + Reference::fromConst(this, Encode::undefined()).storeOnStack(_returnAddress); + + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); if (ast->block) { - int lhs = _block->newTemp(); - move(_block->TEMP(lhs), *expression(ast->expression)); - IR::BasicBlock *switchcond = _function->newBasicBlock(exceptionHandler()); - _block->JUMP(switchcond); - IR::BasicBlock *previousBlock = 0; + BytecodeGenerator::Label switchEnd = bytecodeGenerator->newLabel(); - QHash<Node *, IR::BasicBlock *> blockMap; + Reference lhs = expression(ast->expression); + if (hasError) + return false; + lhs = lhs.storeOnStack(); - enterLoop(ast, switchend, 0); + ControlFlowBlock controlFlow(this, ast->block); + // set up labels for all clauses + QHash<Node *, BytecodeGenerator::Label> blockMap; + for (CaseClauses *it = ast->block->clauses; it; it = it->next) + blockMap[it->clause] = bytecodeGenerator->newLabel(); + if (ast->block->defaultClause) + blockMap[ast->block->defaultClause] = bytecodeGenerator->newLabel(); + for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) + blockMap[it->clause] = bytecodeGenerator->newLabel(); + + // do the switch conditions for (CaseClauses *it = ast->block->clauses; it; it = it->next) { CaseClause *clause = it->clause; - - _block = _function->newBasicBlock(exceptionHandler()); - blockMap[clause] = _block; - - if (previousBlock && !previousBlock->isTerminated()) - previousBlock->JUMP(_block); - - for (StatementList *it2 = clause->statements; it2; it2 = it2->next) - statement(it2->statement); - - previousBlock = _block; - } - - if (ast->block->defaultClause) { - _block = _function->newBasicBlock(exceptionHandler()); - blockMap[ast->block->defaultClause] = _block; - - if (previousBlock && !previousBlock->isTerminated()) - previousBlock->JUMP(_block); - - for (StatementList *it2 = ast->block->defaultClause->statements; it2; it2 = it2->next) - statement(it2->statement); - - previousBlock = _block; + Reference rhs = expression(clause->expression); + if (hasError) + return false; + rhs.loadInAccumulator(); + bytecodeGenerator->jumpStrictEqual(lhs.stackSlot(), blockMap.value(clause)); } for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) { CaseClause *clause = it->clause; + Reference rhs = expression(clause->expression); + if (hasError) + return false; + rhs.loadInAccumulator(); + bytecodeGenerator->jumpStrictEqual(lhs.stackSlot(), blockMap.value(clause)); + } - _block = _function->newBasicBlock(exceptionHandler()); - blockMap[clause] = _block; + if (DefaultClause *defaultClause = ast->block->defaultClause) + bytecodeGenerator->jump().link(blockMap.value(defaultClause)); + else + bytecodeGenerator->jump().link(switchEnd); - if (previousBlock && !previousBlock->isTerminated()) - previousBlock->JUMP(_block); + ControlFlowLoop flow(this, &switchEnd); - for (StatementList *it2 = clause->statements; it2; it2 = it2->next) - statement(it2->statement); + insideSwitch = true; + blockTailCalls.unblock(); + for (CaseClauses *it = ast->block->clauses; it; it = it->next) { + CaseClause *clause = it->clause; + blockMap[clause].link(); - previousBlock = _block; + statementList(clause->statements); } - leaveLoop(); - - _block->JUMP(switchend); + if (ast->block->defaultClause) { + DefaultClause *clause = ast->block->defaultClause; + blockMap[clause].link(); - _block = switchcond; - for (CaseClauses *it = ast->block->clauses; it; it = it->next) { - CaseClause *clause = it->clause; - Result rhs = expression(clause->expression); - IR::BasicBlock *iftrue = blockMap[clause]; - IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler()); - setLocation(cjump(binop(IR::OpStrictEqual, _block->TEMP(lhs), *rhs), iftrue, iffalse), clause->caseToken); - _block = iffalse; + statementList(clause->statements); } for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) { CaseClause *clause = it->clause; - Result rhs = expression(clause->expression); - IR::BasicBlock *iftrue = blockMap[clause]; - IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler()); - setLocation(cjump(binop(IR::OpStrictEqual, _block->TEMP(lhs), *rhs), iftrue, iffalse), clause->caseToken); - _block = iffalse; - } + blockMap[clause].link(); - if (DefaultClause *defaultClause = ast->block->defaultClause) { - setLocation(_block->JUMP(blockMap[ast->block->defaultClause]), defaultClause->defaultToken); + statementList(clause->statements); } - } + insideSwitch = false; + + switchEnd.link(); - _block->JUMP(switchend); + } - _block = switchend; return false; } bool Codegen::visit(ThrowStatement *ast) { if (hasError) - return true; + return false; - Result expr = expression(ast->expression); - move(_block->TEMP(_returnAddress), *expr); - IR::ExprList *throwArgs = _function->New<IR::ExprList>(); - throwArgs->expr = _block->TEMP(_returnAddress); - _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_throw, /*line*/0, /*column*/0), throwArgs)); - return false; -} + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); -bool Codegen::visit(TryStatement *ast) -{ + Reference expr = expression(ast->expression); if (hasError) - return true; - - _function->hasTry = true; - - if (_function->isStrict && ast->catchExpression && - (ast->catchExpression->name == QLatin1String("eval") || ast->catchExpression->name == QLatin1String("arguments"))) { - throwSyntaxError(ast->catchExpression->identifierToken, QStringLiteral("Catch variable name may not be eval or arguments in strict mode")); return false; - } - IR::BasicBlock *surroundingExceptionHandler = exceptionHandler(); - - // We always need a finally body to clean up the exception handler - // exceptions thrown in finally get caught by the surrounding catch block - IR::BasicBlock *finallyBody = 0; - IR::BasicBlock *catchBody = 0; - IR::BasicBlock *catchExceptionHandler = 0; - IR::BasicBlock *end = _function->newBasicBlock(surroundingExceptionHandler, IR::Function::DontInsertBlock); - - if (ast->finallyExpression) - finallyBody = _function->newBasicBlock(surroundingExceptionHandler, IR::Function::DontInsertBlock); + expr.loadInAccumulator(); + Instruction::ThrowException instr; + bytecodeGenerator->addInstruction(instr); + return false; +} - if (ast->catchExpression) { - // exception handler for the catch body - catchExceptionHandler = _function->newBasicBlock(0, IR::Function::DontInsertBlock); - pushExceptionHandler(catchExceptionHandler); - catchBody = _function->newBasicBlock(catchExceptionHandler, IR::Function::DontInsertBlock); - popExceptionHandler(); - pushExceptionHandler(catchBody); - } else { - Q_ASSERT(finallyBody); - pushExceptionHandler(finallyBody); +void Codegen::handleTryCatch(TryStatement *ast) +{ + Q_ASSERT(ast); + RegisterScope scope(this); + { + ControlFlowCatch catchFlow(this, ast->catchExpression); + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); // IMPORTANT: destruction will unblock tail calls before catch is generated + statement(ast->statement); } +} - IR::BasicBlock *tryBody = _function->newBasicBlock(exceptionHandler()); - _block->JUMP(tryBody); - - ScopeAndFinally tcf(_scopeAndFinally, ast->finallyExpression); - _scopeAndFinally = &tcf; - - _block = tryBody; - statement(ast->statement); - _block->JUMP(finallyBody ? finallyBody : end); - - popExceptionHandler(); +void Codegen::handleTryFinally(TryStatement *ast) +{ + RegisterScope scope(this); + ControlFlowFinally finally(this, ast->finallyExpression); + TailCallBlocker blockTailCalls(this); // IMPORTANT: destruction will unblock tail calls before finally is generated if (ast->catchExpression) { - pushExceptionHandler(catchExceptionHandler); - _function->addBasicBlock(catchBody); - _block = catchBody; - - ++_function->insideWithOrCatch; - IR::ExprList *catchArgs = _function->New<IR::ExprList>(); - catchArgs->init(_block->STRING(_function->newString(ast->catchExpression->name.toString()))); - _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_push_catch_scope, 0, 0), catchArgs)); - { - ScopeAndFinally scope(_scopeAndFinally, ScopeAndFinally::CatchScope); - _scopeAndFinally = &scope; - statement(ast->catchExpression->statement); - _scopeAndFinally = scope.parent; - } - _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0), 0)); - --_function->insideWithOrCatch; - _block->JUMP(finallyBody ? finallyBody : end); - popExceptionHandler(); - - _function->addBasicBlock(catchExceptionHandler); - catchExceptionHandler->EXP(catchExceptionHandler->CALL(catchExceptionHandler->NAME(IR::Name::builtin_pop_scope, 0, 0), 0)); - if (finallyBody || surroundingExceptionHandler) - catchExceptionHandler->JUMP(finallyBody ? finallyBody : surroundingExceptionHandler); - else - catchExceptionHandler->EXP(catchExceptionHandler->CALL(catchExceptionHandler->NAME(IR::Name::builtin_rethrow, 0, 0), 0)); + handleTryCatch(ast); + } else { + RegisterScope scope(this); + statement(ast->statement); } +} - _scopeAndFinally = tcf.parent; - - if (finallyBody) { - _function->addBasicBlock(finallyBody); - _block = finallyBody; - - int hasException = _block->newTemp(); - move(_block->TEMP(hasException), _block->CALL(_block->NAME(IR::Name::builtin_unwind_exception, /*line*/0, /*column*/0), 0)); +bool Codegen::visit(TryStatement *ast) +{ + if (hasError) + return false; - if (ast->finallyExpression && ast->finallyExpression->statement) - statement(ast->finallyExpression->statement); + RegisterScope scope(this); - IR::ExprList *arg = _function->New<IR::ExprList>(); - arg->expr = _block->TEMP(hasException); - _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_throw, /*line*/0, /*column*/0), arg)); - _block->JUMP(end); + if (ast->finallyExpression && ast->finallyExpression->statement) { + handleTryFinally(ast); + } else { + handleTryCatch(ast); } - _function->addBasicBlock(end); - _block = end; - return false; } -void Codegen::unwindException(Codegen::ScopeAndFinally *outest) -{ - int savedDepthForWidthOrCatch = _function->insideWithOrCatch; - ScopeAndFinally *scopeAndFinally = _scopeAndFinally; - qSwap(_scopeAndFinally, scopeAndFinally); - while (_scopeAndFinally != outest) { - switch (_scopeAndFinally->type) { - case ScopeAndFinally::WithScope: - // fall through - case ScopeAndFinally::CatchScope: - _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0))); - _scopeAndFinally = _scopeAndFinally->parent; - --_function->insideWithOrCatch; - break; - case ScopeAndFinally::TryScope: { - ScopeAndFinally *tc = _scopeAndFinally; - _scopeAndFinally = tc->parent; - if (tc->finally && tc->finally->statement) - statement(tc->finally->statement); - break; - } - } - } - qSwap(_scopeAndFinally, scopeAndFinally); - _function->insideWithOrCatch = savedDepthForWidthOrCatch; -} - bool Codegen::visit(VariableStatement *ast) { if (hasError) - return true; + return false; variableDeclarationList(ast->declarations); return false; @@ -2820,128 +3698,110 @@ bool Codegen::visit(VariableStatement *ast) bool Codegen::visit(WhileStatement *ast) { if (hasError) - return true; + return false; - IR::BasicBlock *whilecond = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *whilebody = _function->newBasicBlock(exceptionHandler()); - IR::BasicBlock *whileend = _function->newBasicBlock(exceptionHandler()); + if (AST::cast<FalseLiteral *>(ast->expression)) + return false; - enterLoop(ast, whileend, whilecond); + RegisterScope scope(this); - _block->JUMP(whilecond); - _block = whilecond; - condition(ast->expression, whilebody, whileend); + BytecodeGenerator::Label start = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label cond = bytecodeGenerator->label(); + ControlFlowLoop flow(this, &end, &cond); + bytecodeGenerator->addLoopStart(cond); - _block = whilebody; - statement(ast->statement); - setJumpOutLocation(_block->JUMP(whilecond), ast->statement, ast->whileToken); + if (!AST::cast<TrueLiteral *>(ast->expression)) { + TailCallBlocker blockTailCalls(this); + condition(ast->expression, &start, &end, true); + } - _block = whileend; - leaveLoop(); + start.link(); + statement(ast->statement); + setJumpOutLocation(bytecodeGenerator, ast->statement, ast->whileToken); + bytecodeGenerator->jump().link(cond); + end.link(); return false; } bool Codegen::visit(WithStatement *ast) { if (hasError) - return true; + return false; - _function->hasWith = true; + RegisterScope scope(this); + TailCallBlocker blockTailCalls(this); - const int withObject = _block->newTemp(); - Result src = expression(ast->expression); + Reference src = expression(ast->expression); if (hasError) return false; - _block->MOVE(_block->TEMP(withObject), *src); - - // need an exception handler for with to cleanup the with scope - IR::BasicBlock *withExceptionHandler = _function->newBasicBlock(exceptionHandler()); - withExceptionHandler->EXP(withExceptionHandler->CALL(withExceptionHandler->NAME(IR::Name::builtin_pop_scope, 0, 0), 0)); - if (!exceptionHandler()) - withExceptionHandler->EXP(withExceptionHandler->CALL(withExceptionHandler->NAME(IR::Name::builtin_rethrow, 0, 0), 0)); - else - withExceptionHandler->JUMP(exceptionHandler()); - - pushExceptionHandler(withExceptionHandler); - - IR::BasicBlock *withBlock = _function->newBasicBlock(exceptionHandler()); - - _block->JUMP(withBlock); - _block = withBlock; - IR::ExprList *args = _function->New<IR::ExprList>(); - args->init(_block->TEMP(withObject)); - _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_push_with_scope, 0, 0), args)); + src = src.storeOnStack(); // trigger load before we setup the exception handler, so exceptions here go to the right place + src.loadInAccumulator(); - ++_function->insideWithOrCatch; + enterContext(ast); { - ScopeAndFinally scope(_scopeAndFinally); - _scopeAndFinally = &scope; + blockTailCalls.unblock(); + ControlFlowWith flow(this); statement(ast->statement); - _scopeAndFinally = scope.parent; } - --_function->insideWithOrCatch; - _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0), 0)); - popExceptionHandler(); - - IR::BasicBlock *next = _function->newBasicBlock(exceptionHandler()); - _block->JUMP(next); - _block = next; + leaveContext(); return false; } bool Codegen::visit(UiArrayBinding *) { - Q_ASSERT(!"not implemented"); + Q_UNIMPLEMENTED(); return false; } bool Codegen::visit(UiObjectBinding *) { - Q_ASSERT(!"not implemented"); + Q_UNIMPLEMENTED(); return false; } bool Codegen::visit(UiObjectDefinition *) { - Q_ASSERT(!"not implemented"); + Q_UNIMPLEMENTED(); return false; } bool Codegen::visit(UiPublicMember *) { - Q_ASSERT(!"not implemented"); + Q_UNIMPLEMENTED(); return false; } bool Codegen::visit(UiScriptBinding *) { - Q_ASSERT(!"not implemented"); + Q_UNIMPLEMENTED(); return false; } bool Codegen::visit(UiSourceElement *) { - Q_ASSERT(!"not implemented"); + Q_UNIMPLEMENTED(); return false; } -bool Codegen::throwSyntaxErrorOnEvalOrArgumentsInStrictMode(IR::Expr *expr, const SourceLocation& loc) +bool Codegen::throwSyntaxErrorOnEvalOrArgumentsInStrictMode(const Reference &r, const SourceLocation& loc) { - if (!_variableEnvironment->isStrict) - return false; - if (IR::Name *n = expr->asName()) { - if (*n->id != QLatin1String("eval") && *n->id != QLatin1String("arguments")) - return false; - } else if (IR::ArgLocal *al = expr->asArgLocal()) { - if (!al->isArgumentsOrEval) - return false; - } else { + if (!_context->isStrict) return false; + bool isArgOrEval = false; + if (r.type == Reference::Name) { + QString str = jsUnitGenerator->stringForIndex(r.nameAsIndex()); + if (str == QLatin1String("eval") || str == QLatin1String("arguments")) { + isArgOrEval = true; + } + } else if (r.type == Reference::ScopedLocal || r.isRegister()) { + isArgOrEval = r.isArgOrEval; } - throwSyntaxError(loc, QStringLiteral("Variable name may not be eval or arguments in strict mode")); - return true; + if (isArgOrEval) + throwSyntaxError(loc, QStringLiteral("Variable name may not be eval or arguments in strict mode")); + return isArgOrEval; } void Codegen::throwSyntaxError(const SourceLocation &loc, const QString &detail) @@ -2973,6 +3833,117 @@ QList<QQmlJS::DiagnosticMessage> Codegen::errors() const return _errors; } +QQmlRefPointer<CompiledData::CompilationUnit> Codegen::generateCompilationUnit(bool generateUnitData) +{ + CompiledData::Unit *unitData = nullptr; + if (generateUnitData) + unitData = jsUnitGenerator->generateUnit(); + CompiledData::CompilationUnit *compilationUnit = new CompiledData::CompilationUnit(unitData); + + QQmlRefPointer<CompiledData::CompilationUnit> unit; + unit.adopt(compilationUnit); + return unit; +} + +QQmlRefPointer<CompiledData::CompilationUnit> Codegen::createUnitForLoading() +{ + QQmlRefPointer<CompiledData::CompilationUnit> result; + result.adopt(new CompiledData::CompilationUnit); + return result; +} + +class Codegen::VolatileMemoryLocationScanner: protected QQmlJS::AST::Visitor +{ + VolatileMemoryLocations locs; + +public: + Codegen::VolatileMemoryLocations scan(AST::Node *s) + { + s->accept(this); + return locs; + } + + bool visit(ArrayMemberExpression *) override + { + locs.setAllVolatile(); + return false; + } + + bool visit(FieldMemberExpression *) override + { + locs.setAllVolatile(); + return false; + } + + bool visit(PostIncrementExpression *e) override + { + collectIdentifiers(locs.specificLocations, e->base); + return false; + } + + bool visit(PostDecrementExpression *e) override + { + collectIdentifiers(locs.specificLocations, e->base); + return false; + } + + bool visit(PreIncrementExpression *e) override + { + collectIdentifiers(locs.specificLocations, e->expression); + return false; + } + + bool visit(PreDecrementExpression *e) override + { + collectIdentifiers(locs.specificLocations, e->expression); + return false; + } + + bool visit(BinaryExpression *e) override + { + switch (e->op) { + case QSOperator::InplaceAnd: + case QSOperator::InplaceSub: + case QSOperator::InplaceDiv: + case QSOperator::InplaceAdd: + case QSOperator::InplaceLeftShift: + case QSOperator::InplaceMod: + case QSOperator::InplaceMul: + case QSOperator::InplaceOr: + case QSOperator::InplaceRightShift: + case QSOperator::InplaceURightShift: + case QSOperator::InplaceXor: + collectIdentifiers(locs.specificLocations, e); + return false; + + default: + return true; + } + } + +private: + void collectIdentifiers(QVector<QStringView> &ids, AST::Node *node) const { + class Collector: public QQmlJS::AST::Visitor { + QVector<QStringView> &ids; + public: + Collector(QVector<QStringView> &ids): ids(ids) {} + virtual bool visit(IdentifierExpression *ie) { + ids.append(ie->name); + return false; + } + }; + Collector collector(ids); + node->accept(&collector); + } +}; + +Codegen::VolatileMemoryLocations Codegen::scanVolatileMemoryLocations(AST::Node *ast) const +{ + VolatileMemoryLocationScanner scanner; + return scanner.scan(ast); +} + + #ifndef V4_BOOTSTRAP QList<QQmlError> Codegen::qmlErrors() const @@ -2998,20 +3969,489 @@ QList<QQmlError> Codegen::qmlErrors() const return qmlErrors; } -void RuntimeCodegen::throwSyntaxError(const AST::SourceLocation &loc, const QString &detail) +#endif // V4_BOOTSTRAP + +bool Codegen::RValue::operator==(const RValue &other) const { - if (hasError) + switch (type) { + case Accumulator: + return other.isAccumulator(); + case StackSlot: + return other.isStackSlot() && theStackSlot == other.theStackSlot; + case Const: + return other.isConst() && constant == other.constant; + default: + return false; + } +} + +Codegen::RValue Codegen::RValue::storeOnStack() const +{ + switch (type) { + case Accumulator: + return RValue::fromStackSlot(codegen, Reference::fromAccumulator(codegen).storeOnStack().stackSlot()); + case StackSlot: + return *this; + case Const: + return RValue::fromStackSlot(codegen, Reference::storeConstOnStack(codegen, constant).stackSlot()); + default: + Q_UNREACHABLE(); + } +} + +void Codegen::RValue::loadInAccumulator() const +{ + switch (type) { + case Accumulator: + // nothing to do return; - hasError = true; - engine->throwSyntaxError(detail, _module->fileName, loc.startLine, loc.startColumn); + case StackSlot: + return Reference::fromStackSlot(codegen, theStackSlot).loadInAccumulator(); + case Const: + return Reference::fromConst(codegen, constant).loadInAccumulator(); + default: + Q_UNREACHABLE(); + } + } -void RuntimeCodegen::throwReferenceError(const AST::SourceLocation &loc, const QString &detail) +bool Codegen::Reference::operator==(const Codegen::Reference &other) const { - if (hasError) + if (type != other.type) + return false; + switch (type) { + case Invalid: + case Accumulator: + break; + case Super: + return true; + case SuperProperty: + return property == other.property; + case StackSlot: + return theStackSlot == other.theStackSlot; + case ScopedLocal: + return index == other.index && scope == other.scope; + case Name: + return nameAsIndex() == other.nameAsIndex(); + case Member: + return propertyBase == other.propertyBase && propertyNameIndex == other.propertyNameIndex; + case Subscript: + return elementBase == other.elementBase && elementSubscript == other.elementSubscript; + case Import: + return index == other.index; + case Const: + return constant == other.constant; + case QmlScopeObject: + case QmlContextObject: + return qmlCoreIndex == other.qmlCoreIndex && qmlNotifyIndex == other.qmlNotifyIndex + && capturePolicy == other.capturePolicy; + } + return true; +} + +Codegen::RValue Codegen::Reference::asRValue() const +{ + switch (type) { + case Invalid: + Q_UNREACHABLE(); + case Accumulator: + return RValue::fromAccumulator(codegen); + case StackSlot: + return RValue::fromStackSlot(codegen, stackSlot()); + case Const: + return RValue::fromConst(codegen, constant); + default: + loadInAccumulator(); + return RValue::fromAccumulator(codegen); + } +} + +Codegen::Reference Codegen::Reference::asLValue() const +{ + switch (type) { + case Invalid: + case Accumulator: + Q_UNREACHABLE(); + case Super: + codegen->throwSyntaxError(AST::SourceLocation(), QStringLiteral("Super lvalues not implemented.")); + return *this; + case Member: + if (!propertyBase.isStackSlot()) { + Reference r = *this; + r.propertyBase = propertyBase.storeOnStack(); + return r; + } + return *this; + case Subscript: + if (!elementSubscript.isStackSlot()) { + Reference r = *this; + r.elementSubscript = elementSubscript.storeOnStack(); + return r; + } + return *this; + default: + return *this; + } +} + +Codegen::Reference Codegen::Reference::storeConsumeAccumulator() const +{ + storeAccumulator(); // it doesn't matter what happens here, just do it. + return Reference(); +} + +Codegen::Reference Codegen::Reference::baseObject() const +{ + if (type == Reference::QmlScopeObject || type == Reference::QmlContextObject) { + return Reference::fromStackSlot(codegen, qmlBase.stackSlot()); + } else if (type == Reference::Member) { + RValue rval = propertyBase; + if (!rval.isValid()) + return Reference::fromConst(codegen, Encode::undefined()); + if (rval.isAccumulator()) + return Reference::fromAccumulator(codegen); + if (rval.isStackSlot()) + return Reference::fromStackSlot(codegen, rval.stackSlot()); + if (rval.isConst()) + return Reference::fromConst(codegen, rval.constantValue()); + Q_UNREACHABLE(); + } else if (type == Reference::Subscript) { + return Reference::fromStackSlot(codegen, elementBase.stackSlot()); + } else if (type == Reference::SuperProperty) { + return Reference::fromStackSlot(codegen, CallData::This); + } else { + return Reference::fromConst(codegen, Encode::undefined()); + } +} + +Codegen::Reference Codegen::Reference::storeOnStack() const +{ return doStoreOnStack(-1); } + +void Codegen::Reference::storeOnStack(int slotIndex) const +{ doStoreOnStack(slotIndex); } + +Codegen::Reference Codegen::Reference::doStoreOnStack(int slotIndex) const +{ + Q_ASSERT(isValid()); + + if (isStackSlot() && slotIndex == -1 && !(stackSlotIsLocalOrArgument && isVolatile) && !requiresTDZCheck) + return *this; + + if (isStackSlot() && !requiresTDZCheck) { // temp-to-temp move + Reference dest = Reference::fromStackSlot(codegen, slotIndex); + Instruction::MoveReg move; + move.srcReg = stackSlot(); + move.destReg = dest.stackSlot(); + codegen->bytecodeGenerator->addInstruction(move); + return dest; + } + + Reference slot = Reference::fromStackSlot(codegen, slotIndex); + if (isConstant()) { + Instruction::MoveConst move; + move.constIndex = codegen->registerConstant(constant); + move.destTemp = slot.stackSlot(); + codegen->bytecodeGenerator->addInstruction(move); + } else { + loadInAccumulator(); + slot.storeConsumeAccumulator(); + } + return slot; +} + +Codegen::Reference Codegen::Reference::storeRetainAccumulator() const +{ + if (storeWipesAccumulator()) { + // a store will + auto tmp = Reference::fromStackSlot(codegen); + tmp.storeAccumulator(); // this is safe, and won't destory the accumulator + storeAccumulator(); + return tmp; + } else { + // ok, this is safe, just do the store. + storeAccumulator(); + return *this; + } +} + +bool Codegen::Reference::storeWipesAccumulator() const +{ + switch (type) { + default: + case Invalid: + case Const: + case Accumulator: + Q_UNREACHABLE(); + return false; + case StackSlot: + case ScopedLocal: + return false; + case Name: + case Member: + case Subscript: + case QmlScopeObject: + case QmlContextObject: + return true; + } +} + +void Codegen::Reference::storeAccumulator() const +{ + if (isReferenceToConst) { + // throw a type error + RegisterScope scope(codegen); + Reference r = codegen->referenceForName(QStringLiteral("TypeError"), false); + r = r.storeOnStack(); + Instruction::Construct construct; + construct.func = r.stackSlot(); + construct.argc = 0; + construct.argv = 0; + codegen->bytecodeGenerator->addInstruction(construct); + Instruction::ThrowException throwException; + codegen->bytecodeGenerator->addInstruction(throwException); return; - hasError = true; - engine->throwReferenceError(detail, _module->fileName, loc.startLine, loc.startColumn); + } + switch (type) { + case Super: + Q_UNREACHABLE(); + return; + case SuperProperty: + Instruction::StoreSuperProperty store; + store.property = property.stackSlot(); + codegen->bytecodeGenerator->addInstruction(store); + return; + case StackSlot: { + Instruction::StoreReg store; + store.reg = theStackSlot; + codegen->bytecodeGenerator->addInstruction(store); + return; + } + case ScopedLocal: { + if (scope == 0) { + Instruction::StoreLocal store; + store.index = index; + codegen->bytecodeGenerator->addInstruction(store); + } else { + Instruction::StoreScopedLocal store; + store.index = index; + store.scope = scope; + codegen->bytecodeGenerator->addInstruction(store); + } + return; + } + case Name: { + Context *c = codegen->currentContext(); + if (c->isStrict) { + Instruction::StoreNameStrict store; + store.name = nameAsIndex(); + codegen->bytecodeGenerator->addInstruction(store); + } else { + Instruction::StoreNameSloppy store; + store.name = nameAsIndex(); + codegen->bytecodeGenerator->addInstruction(store); + } + } return; + case Member: + if (!disable_lookups && codegen->useFastLookups) { + Instruction::SetLookup store; + store.base = propertyBase.stackSlot(); + store.index = codegen->registerSetterLookup(propertyNameIndex); + codegen->bytecodeGenerator->addInstruction(store); + } else { + Instruction::StoreProperty store; + store.base = propertyBase.stackSlot(); + store.name = propertyNameIndex; + codegen->bytecodeGenerator->addInstruction(store); + } + return; + case Subscript: { + Instruction::StoreElement store; + store.base = elementBase; + store.index = elementSubscript.stackSlot(); + codegen->bytecodeGenerator->addTracingInstruction(store); + } return; + case QmlScopeObject: { + Instruction::StoreScopeObjectProperty store; + store.base = qmlBase; + store.propertyIndex = qmlCoreIndex; + codegen->bytecodeGenerator->addInstruction(store); + } return; + case QmlContextObject: { + Instruction::StoreContextObjectProperty store; + store.base = qmlBase; + store.propertyIndex = qmlCoreIndex; + codegen->bytecodeGenerator->addInstruction(store); + } return; + case Invalid: + case Accumulator: + case Const: + case Import: + break; + } + + Q_UNREACHABLE(); } -#endif // V4_BOOTSTRAP +void Codegen::Reference::loadInAccumulator() const +{ + auto tdzCheck = [this](bool requiresCheck){ + if (!requiresCheck) + return; + Instruction::DeadTemporalZoneCheck check; + check.name = codegen->registerString(name); + codegen->bytecodeGenerator->addInstruction(check); + }; + auto tdzCheckStackSlot = [this, tdzCheck](Moth::StackSlot slot, bool requiresCheck){ + if (!requiresCheck) + return; + Instruction::LoadReg load; + load.reg = slot; + codegen->bytecodeGenerator->addInstruction(load); + tdzCheck(true); + }; + + switch (type) { + case Accumulator: + return; + case Super: + Q_UNREACHABLE(); + return; + case SuperProperty: + tdzCheckStackSlot(property, subscriptRequiresTDZCheck); + Instruction::LoadSuperProperty load; + load.property = property.stackSlot(); + codegen->bytecodeGenerator->addInstruction(load); + return; + case Const: { +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty structs. + if (constant == Encode::null()) { + Instruction::LoadNull load; + codegen->bytecodeGenerator->addInstruction(load); + } else if (constant == Encode(true)) { + Instruction::LoadTrue load; + codegen->bytecodeGenerator->addInstruction(load); + } else if (constant == Encode(false)) { + Instruction::LoadFalse load; + codegen->bytecodeGenerator->addInstruction(load); + } else if (constant == Encode::undefined()) { + Instruction::LoadUndefined load; + codegen->bytecodeGenerator->addInstruction(load); + } else { + Value p = Value::fromReturnedValue(constant); + if (p.isNumber()) { + double d = p.asDouble(); + int i = static_cast<int>(d); + if (d == i && (d != 0 || !std::signbit(d))) { + if (!i) { + Instruction::LoadZero load; + codegen->bytecodeGenerator->addInstruction(load); + return; + } + Instruction::LoadInt load; + load.value = Value::fromReturnedValue(constant).toInt32(); + codegen->bytecodeGenerator->addInstruction(load); + return; + } + } + Instruction::LoadConst load; + load.index = codegen->registerConstant(constant); + codegen->bytecodeGenerator->addInstruction(load); + } +QT_WARNING_POP + } return; + case StackSlot: { + Instruction::LoadReg load; + load.reg = stackSlot(); + codegen->bytecodeGenerator->addInstruction(load); + tdzCheck(requiresTDZCheck); + } return; + case ScopedLocal: { + if (!scope) { + Instruction::LoadLocal load; + load.index = index; + codegen->bytecodeGenerator->addTracingInstruction(load); + } else { + Instruction::LoadScopedLocal load; + load.index = index; + load.scope = scope; + codegen->bytecodeGenerator->addTracingInstruction(load); + } + tdzCheck(requiresTDZCheck); + return; + } + case Name: + if (global) { + // these value properties of the global object are immutable, we we can directly convert them + // to their numeric value here + if (name == QStringLiteral("undefined")) { + Reference::fromConst(codegen, Encode::undefined()).loadInAccumulator(); + return; + } else if (name == QStringLiteral("Infinity")) { + Reference::fromConst(codegen, Encode(qInf())).loadInAccumulator(); + return; + } else if (name == QStringLiteral("Nan")) { + Reference::fromConst(codegen, Encode(qQNaN())).loadInAccumulator(); + return; + } + } + if (!disable_lookups && global) { + Instruction::LoadGlobalLookup load; + load.index = codegen->registerGlobalGetterLookup(nameAsIndex()); + codegen->bytecodeGenerator->addTracingInstruction(load); + } else { + Instruction::LoadName load; + load.name = nameAsIndex(); + codegen->bytecodeGenerator->addTracingInstruction(load); + } + return; + case Member: + propertyBase.loadInAccumulator(); + tdzCheck(requiresTDZCheck); + if (!disable_lookups && codegen->useFastLookups) { + Instruction::GetLookup load; + load.index = codegen->registerGetterLookup(propertyNameIndex); + codegen->bytecodeGenerator->addTracingInstruction(load); + } else { + Instruction::LoadProperty load; + load.name = propertyNameIndex; + codegen->bytecodeGenerator->addTracingInstruction(load); + } + return; + case Import: { + Instruction::LoadImport load; + load.index = index; + codegen->bytecodeGenerator->addInstruction(load); + tdzCheck(requiresTDZCheck); + } return; + case Subscript: { + tdzCheckStackSlot(elementBase, requiresTDZCheck); + elementSubscript.loadInAccumulator(); + tdzCheck(subscriptRequiresTDZCheck); + Instruction::LoadElement load; + load.base = elementBase; + codegen->bytecodeGenerator->addTracingInstruction(load); + } return; + case QmlScopeObject: { + Instruction::LoadScopeObjectProperty load; + load.base = qmlBase; + load.propertyIndex = qmlCoreIndex; + load.captureRequired = capturePolicy == CaptureAtRuntime; + codegen->bytecodeGenerator->addInstruction(load); + if (capturePolicy == CaptureAheadOfTime) + codegen->_context->scopeObjectPropertyDependencies.insert(qmlCoreIndex, qmlNotifyIndex); + } return; + case QmlContextObject: { + Instruction::LoadContextObjectProperty load; + load.base = qmlBase; + load.propertyIndex = qmlCoreIndex; + load.captureRequired = capturePolicy == CaptureAtRuntime; + codegen->bytecodeGenerator->addInstruction(load); + if (capturePolicy == CaptureAheadOfTime) + codegen->_context->contextObjectPropertyDependencies.insert(qmlCoreIndex, qmlNotifyIndex); + } return; + case Invalid: + break; + } + Q_UNREACHABLE(); +} diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 239ed5c4b9..4d7001fe64 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQml module of the Qt Toolkit. @@ -51,303 +51,524 @@ // #include "private/qv4global_p.h" -#include "qv4jsir_p.h" #include <private/qqmljsastvisitor_p.h> #include <private/qqmljsast_p.h> -#include <private/qqmljsengine_p.h> -#include <QtCore/QStringList> -#include <QStack> -#ifndef V4_BOOTSTRAP -#include <qqmlerror.h> -#endif +#include <private/qv4compiler_p.h> +#include <private/qv4compilercontext_p.h> #include <private/qv4util_p.h> +#include <private/qv4bytecodegenerator_p.h> +#include <private/qv4stackframe_p.h> QT_BEGIN_NAMESPACE -namespace QQmlJS { -namespace AST { -class UiParameterList; +using namespace QQmlJS; + +namespace QV4 { + +namespace Moth { +struct Instruction; +} + +namespace CompiledData { +struct CompilationUnit; } +namespace Compiler { + +struct ControlFlow; +struct ControlFlowCatch; +struct ControlFlowFinally; -class Q_QML_PRIVATE_EXPORT Codegen: protected AST::Visitor +class Q_QML_PRIVATE_EXPORT Codegen: protected QQmlJS::AST::Visitor { +protected: + using BytecodeGenerator = QV4::Moth::BytecodeGenerator; + using Instruction = QV4::Moth::Instruction; public: - Codegen(bool strict); - - enum CompilationMode { - GlobalCode, - EvalCode, - FunctionCode, - QmlBinding // This is almost the same as EvalCode, except: - // * function declarations are moved to the return address when encountered - // * return statements are allowed everywhere (like in FunctionCode) - // * variable declarations are treated as true locals (like in FunctionCode) - }; + Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict); + void generateFromProgram(const QString &fileName, + const QString &finalUrl, const QString &sourceCode, AST::Program *ast, - QV4::IR::Module *module, - CompilationMode mode = GlobalCode, - const QStringList &inheritedLocals = QStringList()); - void generateFromFunctionExpression(const QString &fileName, - const QString &sourceCode, - AST::FunctionExpression *ast, - QV4::IR::Module *module); + Module *module, + ContextType contextType = ContextType::Global); -protected: - enum Format { ex, cx, nx }; - struct Result { - QV4::IR::Expr *code; - QV4::IR::BasicBlock *iftrue; - QV4::IR::BasicBlock *iffalse; - Format format; - Format requested; - - explicit Result(Format requested = ex) - : code(0) - , iftrue(0) - , iffalse(0) - , format(ex) - , requested(requested) {} - - explicit Result(QV4::IR::BasicBlock *iftrue, QV4::IR::BasicBlock *iffalse) - : code(0) - , iftrue(iftrue) - , iffalse(iffalse) - , format(ex) - , requested(cx) {} - - inline QV4::IR::Expr *operator*() const { Q_ASSERT(format == ex); return code; } - inline QV4::IR::Expr *operator->() const { Q_ASSERT(format == ex); return code; } + void generateFromModule(const QString &fileName, + const QString &finalUrl, + const QString &sourceCode, + AST::ESModule *ast, + Module *module); - bool accept(Format f) - { - if (requested == f) { - format = f; +public: + class VolatileMemoryLocationScanner; + class VolatileMemoryLocations { + friend VolatileMemoryLocationScanner; + bool allVolatile = false; + QVector<QStringView> specificLocations; + public: + bool isVolatile(const QStringView &name) { + if (allVolatile) return true; - } - return false; + return specificLocations.contains(name); } + + void add(const QStringRef &name) { if (!allVolatile) specificLocations.append(name); } + void setAllVolatile() { allVolatile = true; } }; + class RValue { + Codegen *codegen; + enum Type { + Invalid, + Accumulator, + StackSlot, + Const + } type; + union { + Moth::StackSlot theStackSlot; + QV4::ReturnedValue constant; + }; + + public: + static RValue fromStackSlot(Codegen *codegen, Moth::StackSlot stackSlot) { + RValue r; + r.codegen = codegen; + r.type = StackSlot; + r.theStackSlot = stackSlot; + return r; + } + static RValue fromAccumulator(Codegen *codegen) { + RValue r; + r.codegen = codegen; + r.type = Accumulator; + return r; + } + static RValue fromConst(Codegen *codegen, QV4::ReturnedValue value) { + RValue r; + r.codegen = codegen; + r.type = Const; + r.constant = value; + return r; + } - struct Environment { - Environment *parent; + bool operator==(const RValue &other) const; - enum MemberType { - UndefinedMember, - VariableDefinition, - VariableDeclaration, - FunctionDefinition - }; + bool isValid() const { return type != Invalid; } + bool isAccumulator() const { return type == Accumulator; } + bool isStackSlot() const { return type == StackSlot; } + bool isConst() const { return type == Const; } - struct Member { - MemberType type; - int index; - AST::FunctionExpression *function; - AST::VariableDeclaration::VariableScope scope; + Moth::StackSlot stackSlot() const { + Q_ASSERT(isStackSlot()); + return theStackSlot; + } + + QV4::ReturnedValue constantValue() const { + Q_ASSERT(isConst()); + return constant; + } + + Q_REQUIRED_RESULT RValue storeOnStack() const; + void loadInAccumulator() const; + }; + struct Reference { + enum Type { + Invalid, + Accumulator, + Super, + SuperProperty, + StackSlot, + ScopedLocal, + Name, + Member, + Subscript, + Import, + QmlScopeObject, + QmlContextObject, + LastLValue = QmlContextObject, + Const + } type = Invalid; + + bool isLValue() const { return !isReadonly && type > Accumulator; } + + Reference(Codegen *cg, Type type = Invalid) : type(type), constant(0), codegen(cg) {} + Reference(): constant(0) {} + Reference(const Reference &) = default; + Reference(Reference &&) = default; + Reference &operator =(const Reference &) = default; + Reference &operator =(Reference &&) = default; + + bool operator==(const Reference &other) const; + bool operator!=(const Reference &other) const + { return !(*this == other); } + + bool isValid() const { return type != Invalid; } + bool loadTriggersSideEffect() const { + switch (type) { + case QmlScopeObject: + return capturePolicy != DontCapture; + case QmlContextObject: + return capturePolicy != DontCapture; + case Name: + case Member: + case Subscript: + case SuperProperty: + return true; + default: + return requiresTDZCheck; + } + } + bool isConstant() const { return type == Const; } + bool isAccumulator() const { return type == Accumulator; } + bool isSuper() const { return type == Super; } + bool isSuperProperty() const { return type == SuperProperty; } + bool isStackSlot() const { return type == StackSlot; } + bool isRegister() const { + return isStackSlot(); + } - bool isLexicallyScoped() const { return this->scope != AST::VariableDeclaration::FunctionScope; } + enum PropertyCapturePolicy { + /* + We're reading a property from the scope or context object, but it's a CONSTANT property, + so we don't need to register a dependency at all. + */ + DontCapture, + /* + We're reading the property of a QObject, and we know that it's the + scope object or context object, which we know very well. Instead of registering a + property capture every time, we can do that ahead of time and then register all those + captures in one shot in registerQmlDependencies(). + */ + CaptureAheadOfTime, + /* + We're reading the property of a QObject, and we're not quite sure where + the QObject comes from or what it is. So, when reading that property at run-time, + make sure that we capture where we read that property so that if it changes we can + re-evaluate the entire expression. + */ + CaptureAtRuntime }; - typedef QMap<QString, Member> MemberMap; - - MemberMap members; - AST::FormalParameterList *formals; - int maxNumberOfArguments; - bool hasDirectEval; - bool hasNestedFunctions; - bool isStrict; - bool isNamedFunctionExpression; - bool usesThis; - enum UsesArgumentsObject { - ArgumentsObjectUnknown, - ArgumentsObjectNotUsed, - ArgumentsObjectUsed + + static Reference fromAccumulator(Codegen *cg) { + return Reference(cg, Accumulator); + } + static Reference fromSuper(Codegen *cg) { + return Reference(cg, Super); + } + static Reference fromStackSlot(Codegen *cg, int tempIndex = -1, bool isLocal = false) { + Reference r(cg, StackSlot); + if (tempIndex == -1) + tempIndex = cg->bytecodeGenerator->newRegister(); + r.theStackSlot = Moth::StackSlot::createRegister(tempIndex); + r.stackSlotIsLocalOrArgument = isLocal; + return r; + } + static Reference fromArgument(Codegen *cg, int index, bool isVolatile) { + Reference r(cg, StackSlot); + r.theStackSlot = Moth::StackSlot::createRegister(index + sizeof(CallData)/sizeof(Value) - 1); + r.stackSlotIsLocalOrArgument = true; + r.isVolatile = isVolatile; + return r; + } + static Reference fromScopedLocal(Codegen *cg, int index, int scope) { + Reference r(cg, ScopedLocal); + r.index = index; + r.scope = scope; + return r; + } + static Reference fromImport(Codegen *cg, int index) { + Reference r(cg, Import); + r.index = index; + return r; + } + static Reference fromName(Codegen *cg, const QString &name) { + Reference r(cg, Name); + r.name = name; + return r; + } + static Reference fromMember(const Reference &baseRef, const QString &name) { + Reference r(baseRef.codegen, Member); + r.propertyBase = baseRef.asRValue(); + r.propertyNameIndex = r.codegen->registerString(name); + r.requiresTDZCheck = baseRef.requiresTDZCheck; + return r; + } + static Reference fromSuperProperty(const Reference &property) { + Q_ASSERT(property.isStackSlot()); + Reference r(property.codegen, SuperProperty); + r.property = property.stackSlot(); + r.subscriptRequiresTDZCheck = property.requiresTDZCheck; + return r; + } + static Reference fromSubscript(const Reference &baseRef, const Reference &subscript) { + Q_ASSERT(baseRef.isStackSlot()); + Reference r(baseRef.codegen, Subscript); + r.elementBase = baseRef.stackSlot(); + r.elementSubscript = subscript.asRValue(); + r.requiresTDZCheck = baseRef.requiresTDZCheck; + r.subscriptRequiresTDZCheck = subscript.requiresTDZCheck; + return r; + } + static Reference fromConst(Codegen *cg, QV4::ReturnedValue constant) { + Reference r(cg, Const); + r.constant = constant; + r.isReadonly = true; + return r; + } + static Reference fromQmlScopeObject(const Reference &base, qint16 coreIndex, qint16 notifyIndex, PropertyCapturePolicy capturePolicy) { + Reference r(base.codegen, QmlScopeObject); + r.qmlBase = base.storeOnStack().stackSlot(); + r.qmlCoreIndex = coreIndex; + r.qmlNotifyIndex = notifyIndex; + r.capturePolicy = capturePolicy; + return r; + } + static Reference fromQmlContextObject(const Reference &base, qint16 coreIndex, qint16 notifyIndex, PropertyCapturePolicy capturePolicy) { + Reference r(base.codegen, QmlContextObject); + r.qmlBase = base.storeOnStack().stackSlot(); + r.qmlCoreIndex = coreIndex; + r.qmlNotifyIndex = notifyIndex; + r.capturePolicy = capturePolicy; + return r; + } + static Reference fromThis(Codegen *cg) { + Reference r = fromStackSlot(cg, CallData::This); + r.isReadonly = true; + // ### Optimize this. Functions that are not derived constructors or arrow functions can't have an + // empty this object + r.requiresTDZCheck = true; + return r; + } + + RValue asRValue() const; + Reference asLValue() const; + + Q_REQUIRED_RESULT static Reference storeConstOnStack(Codegen *cg, QV4::ReturnedValue constant) + { return Reference::fromConst(cg, constant).storeOnStack(); } + + static void storeConstOnStack(Codegen *cg, QV4::ReturnedValue constant, int stackSlot) + { Reference::fromConst(cg, constant).storeOnStack(stackSlot); } + + Q_REQUIRED_RESULT Reference storeOnStack() const; + void storeOnStack(int tempIndex) const; + Q_REQUIRED_RESULT Reference storeRetainAccumulator() const; + Reference storeConsumeAccumulator() const; + + Q_REQUIRED_RESULT Reference baseObject() const; + + bool storeWipesAccumulator() const; + void loadInAccumulator() const; + + int nameAsIndex() const { + Q_ASSERT(type == Name); + return codegen->registerString(name); + } + + Moth::StackSlot stackSlot() const { + if (Q_UNLIKELY(!isStackSlot())) + Q_UNREACHABLE(); + return theStackSlot; + } + + union { + Moth::StackSlot theStackSlot; + QV4::ReturnedValue constant; + struct { // Scoped arguments/Local + int index; + int scope; + }; + struct { + RValue propertyBase; + int propertyNameIndex; + }; + struct { + Moth::StackSlot elementBase; + RValue elementSubscript; + }; + struct { // QML scope/context object case + Moth::StackSlot qmlBase; + qint16 qmlCoreIndex; + qint16 qmlNotifyIndex; + PropertyCapturePolicy capturePolicy; + }; + Moth::StackSlot property; // super property }; + QString name; + mutable bool isArgOrEval = false; + bool isReadonly = false; + bool isReferenceToConst = false; + bool requiresTDZCheck = false; + bool subscriptRequiresTDZCheck = false; + bool stackSlotIsLocalOrArgument = false; + bool isVolatile = false; + bool global = false; + Codegen *codegen = nullptr; + + private: + void storeAccumulator() const; + Reference doStoreOnStack(int tempIndex) const; + }; - UsesArgumentsObject usesArgumentsObject; - - CompilationMode compilationMode; - - Environment(Environment *parent, CompilationMode mode) - : parent(parent) - , formals(0) - , maxNumberOfArguments(0) - , hasDirectEval(false) - , hasNestedFunctions(false) - , isStrict(false) - , isNamedFunctionExpression(false) - , usesThis(false) - , usesArgumentsObject(ArgumentsObjectUnknown) - , compilationMode(mode) - { - if (parent && parent->isStrict) - isStrict = true; + struct RegisterScope { + RegisterScope(Codegen *cg) + : generator(cg->bytecodeGenerator), + regCountForScope(generator->currentReg) {} + ~RegisterScope() { + generator->currentReg = regCountForScope; } + BytecodeGenerator *generator; + int regCountForScope; + }; + + struct ObjectPropertyValue { + ObjectPropertyValue() {} + + Reference rvalue; + int getter = -1; // index in _module->functions or -1 if not set + int setter = -1; + uint keyAsIndex = UINT_MAX; + + bool hasGetter() const { return getter >= 0; } + bool hasSetter() const { return setter >= 0; } + }; +protected: + + enum Format { ex, cx, nx }; + class Result { + Reference _result; + + const BytecodeGenerator::Label *_iftrue = nullptr; + const BytecodeGenerator::Label *_iffalse = nullptr; + Format _format = ex; + Format _requested; + bool _trueBlockFollowsCondition = false; + + public: + explicit Result(const Reference &lrvalue) + : _result(lrvalue) + , _requested(ex) + {} - int findMember(const QString &name) const + explicit Result(Format requested = ex) + : _requested(requested) {} + + explicit Result(const BytecodeGenerator::Label *iftrue, + const BytecodeGenerator::Label *iffalse, + bool trueBlockFollowsCondition) + : _iftrue(iftrue) + , _iffalse(iffalse) + , _requested(cx) + , _trueBlockFollowsCondition(trueBlockFollowsCondition) { - MemberMap::const_iterator it = members.find(name); - if (it == members.end()) - return -1; - Q_ASSERT((*it).index != -1 || !parent); - return (*it).index; + Q_ASSERT(iftrue); + Q_ASSERT(iffalse); } - bool memberInfo(const QString &name, const Member **m) const - { - Q_ASSERT(m); - MemberMap::const_iterator it = members.find(name); - if (it == members.end()) { - *m = 0; - return false; - } - *m = &(*it); - return true; + const BytecodeGenerator::Label *iftrue() const { + Q_ASSERT(_requested == cx); + return _iftrue; } - bool lookupMember(const QString &name, Environment **scope, int *index, int *distance) + const BytecodeGenerator::Label *iffalse() const { + Q_ASSERT(_requested == cx); + return _iffalse; + } + + Format format() const { + return _format; + } + + bool accept(Format f) { - Environment *it = this; - *distance = 0; - for (; it; it = it->parent, ++(*distance)) { - int idx = it->findMember(name); - if (idx != -1) { - *scope = it; - *index = idx; - return true; - } + if (_requested == f) { + _format = f; + return true; } return false; } - void enter(const QString &name, MemberType type, AST::VariableDeclaration::VariableScope scope, AST::FunctionExpression *function = 0) - { - if (! name.isEmpty()) { - if (type != FunctionDefinition) { - for (AST::FormalParameterList *it = formals; it; it = it->next) - if (it->name == name) - return; - } - MemberMap::iterator it = members.find(name); - if (it == members.end()) { - Member m; - m.index = -1; - m.type = type; - m.function = function; - m.scope = scope; - members.insert(name, m); - } else { - Q_ASSERT(scope == (*it).scope); - if ((*it).type <= type) { - (*it).type = type; - (*it).function = function; - } - } - } + bool trueBlockFollowsCondition() const { + return _trueBlockFollowsCondition; } - }; - Environment *newEnvironment(AST::Node *node, Environment *parent, CompilationMode compilationMode) - { - Environment *env = new Environment(parent, compilationMode); - _envMap.insert(node, env); - return env; - } + const Reference &result() const { + return _result; + } - struct UiMember { + void setResult(const Reference &result) { + _result = result; + } }; - struct ScopeAndFinally { - enum ScopeType { - WithScope, - TryScope, - CatchScope - }; - - ScopeAndFinally *parent; - AST::Finally *finally; - ScopeType type; + void enterContext(AST::Node *node); + int leaveContext(); +public: + Context *enterBlock(AST::Node *node); + int leaveBlock() { return leaveContext(); } +protected: + void leaveLoop(); - ScopeAndFinally(ScopeAndFinally *parent, ScopeType t = WithScope) : parent(parent), finally(0), type(t) {} - ScopeAndFinally(ScopeAndFinally *parent, AST::Finally *finally) - : parent(parent), finally(finally), type(TryScope) - {} + enum UnaryOperation { + UPlus, + UMinus, + PreIncrement, + PreDecrement, + PostIncrement, + PostDecrement, + Not, + Compl }; - struct Loop { - AST::LabelledStatement *labelledStatement; - AST::Statement *node; - QV4::IR::BasicBlock *breakBlock; - QV4::IR::BasicBlock *continueBlock; - Loop *parent; - ScopeAndFinally *scopeAndFinally; + Reference unop(UnaryOperation op, const Reference &expr); - Loop(AST::Statement *node, QV4::IR::BasicBlock *breakBlock, QV4::IR::BasicBlock *continueBlock, Loop *parent) - : labelledStatement(0), node(node), breakBlock(breakBlock), continueBlock(continueBlock), parent(parent) {} - }; - - void enterEnvironment(AST::Node *node); - void leaveEnvironment(); + void addCJump(); - void enterLoop(AST::Statement *node, QV4::IR::BasicBlock *breakBlock, QV4::IR::BasicBlock *continueBlock); - void leaveLoop(); - QV4::IR::BasicBlock *exceptionHandler() const - { - if (_exceptionHandlers.isEmpty()) - return 0; - return _exceptionHandlers.top(); - } - void pushExceptionHandler(QV4::IR::BasicBlock *handler) - { - handler->setExceptionHandler(true); - _exceptionHandlers.push(handler); - } - void popExceptionHandler() - { - Q_ASSERT(!_exceptionHandlers.isEmpty()); - _exceptionHandlers.pop(); +public: + int registerString(const QString &name) { + return jsUnitGenerator->registerString(name); } - - QV4::IR::Expr *member(QV4::IR::Expr *base, const QString *name); - QV4::IR::Expr *subscript(QV4::IR::Expr *base, QV4::IR::Expr *index); - QV4::IR::Expr *argument(QV4::IR::Expr *expr); - QV4::IR::Expr *reference(QV4::IR::Expr *expr); - QV4::IR::Expr *unop(QV4::IR::AluOp op, QV4::IR::Expr *expr, const AST::SourceLocation &loc = AST::SourceLocation()); - QV4::IR::Expr *binop(QV4::IR::AluOp op, QV4::IR::Expr *left, QV4::IR::Expr *right, const AST::SourceLocation &loc = AST::SourceLocation()); - QV4::IR::Expr *call(QV4::IR::Expr *base, QV4::IR::ExprList *args); - QV4::IR::Stmt *move(QV4::IR::Expr *target, QV4::IR::Expr *source, QV4::IR::AluOp op = QV4::IR::OpInvalid); - QV4::IR::Stmt *cjump(QV4::IR::Expr *cond, QV4::IR::BasicBlock *iftrue, QV4::IR::BasicBlock *iffalse); + int registerConstant(QV4::ReturnedValue v) { return jsUnitGenerator->registerConstant(v); } + int registerGetterLookup(int nameIndex) { return jsUnitGenerator->registerGetterLookup(nameIndex); } + int registerSetterLookup(int nameIndex) { return jsUnitGenerator->registerSetterLookup(nameIndex); } + int registerGlobalGetterLookup(int nameIndex) { return jsUnitGenerator->registerGlobalGetterLookup(nameIndex); } // Returns index in _module->functions - int defineFunction(const QString &name, AST::Node *ast, - AST::FormalParameterList *formals, - AST::SourceElements *body, - const QStringList &inheritedLocals = QStringList()); - - void unwindException(ScopeAndFinally *outest); + virtual int defineFunction(const QString &name, AST::Node *ast, + AST::FormalParameterList *formals, + AST::StatementList *body); +protected: void statement(AST::Statement *ast); void statement(AST::ExpressionNode *ast); - void condition(AST::ExpressionNode *ast, QV4::IR::BasicBlock *iftrue, QV4::IR::BasicBlock *iffalse); - Result expression(AST::ExpressionNode *ast); - Result sourceElement(AST::SourceElement *ast); - UiMember uiObjectMember(AST::UiObjectMember *ast); + void condition(AST::ExpressionNode *ast, const BytecodeGenerator::Label *iftrue, + const BytecodeGenerator::Label *iffalse, + bool trueBlockFollowsCondition); + Reference expression(AST::ExpressionNode *ast); void accept(AST::Node *node); - void functionBody(AST::FunctionBody *ast); void program(AST::Program *ast); - void sourceElements(AST::SourceElements *ast); - void variableDeclaration(AST::VariableDeclaration *ast); + void statementList(AST::StatementList *ast); + void variableDeclaration(AST::PatternElement *ast); void variableDeclarationList(AST::VariableDeclarationList *ast); - QV4::IR::Expr *identifier(const QString &name, int line = 0, int col = 0); - // Hook provided to implement QML lookup semantics - virtual QV4::IR::Expr *fallbackNameLookup(const QString &name, int line, int col); + Reference targetForPatternElement(AST::PatternElement *p); + void initializeAndDestructureBindingElement(AST::PatternElement *e, const Reference &baseRef = Reference(), bool isDefinition = false); + void destructurePropertyList(const Reference &object, AST::PatternPropertyList *bindingList, bool isDefinition = false); + void destructureElementList(const Reference &array, AST::PatternElementList *bindingList, bool isDefinition = false); + void destructurePattern(AST::Pattern *p, const Reference &rhs); + + Reference referenceForPropertyName(const Codegen::Reference &object, AST::PropertyName *name); + + // Hooks provided to implement QML lookup semantics + virtual bool canAccelerateGlobalLookups() const { return true; } + virtual Reference fallbackNameLookup(const QString &name); + virtual void beginFunctionBodyHook() {} + void emitReturn(const Reference &expr); + // nodes bool visit(AST::ArgumentList *ast) override; bool visit(AST::CaseBlock *ast) override; @@ -355,16 +576,10 @@ protected: bool visit(AST::CaseClauses *ast) override; bool visit(AST::Catch *ast) override; bool visit(AST::DefaultClause *ast) override; - bool visit(AST::ElementList *ast) override; bool visit(AST::Elision *ast) override; bool visit(AST::Finally *ast) override; bool visit(AST::FormalParameterList *ast) override; - bool visit(AST::FunctionBody *ast) override; bool visit(AST::Program *ast) override; - bool visit(AST::PropertyNameAndValue *ast) override; - bool visit(AST::PropertyAssignmentList *ast) override; - bool visit(AST::PropertyGetterSetter *ast) override; - bool visit(AST::SourceElements *ast) override; bool visit(AST::StatementList *ast) override; bool visit(AST::UiArrayMemberList *ast) override; bool visit(AST::UiImport *ast) override; @@ -375,20 +590,27 @@ protected: bool visit(AST::UiParameterList *ast) override; bool visit(AST::UiProgram *ast) override; bool visit(AST::UiQualifiedId *ast) override; - bool visit(AST::UiQualifiedPragmaId *ast) override; - bool visit(AST::VariableDeclaration *ast) override; bool visit(AST::VariableDeclarationList *ast) override; + bool visit(AST::PatternElement *ast) override; + bool visit(AST::PatternElementList *ast) override; + bool visit(AST::PatternProperty *ast) override; + bool visit(AST::PatternPropertyList *ast) override; + + bool visit(AST::ExportDeclaration *ast) override; + // expressions bool visit(AST::Expression *ast) override; - bool visit(AST::ArrayLiteral *ast) override; + bool visit(AST::ArrayPattern *ast) override; bool visit(AST::ArrayMemberExpression *ast) override; bool visit(AST::BinaryExpression *ast) override; bool visit(AST::CallExpression *ast) override; bool visit(AST::ConditionalExpression *ast) override; bool visit(AST::DeleteExpression *ast) override; bool visit(AST::FalseLiteral *ast) override; + bool visit(AST::SuperLiteral *ast) override; bool visit(AST::FieldMemberExpression *ast) override; + bool visit(AST::TaggedTemplate *ast) override; bool visit(AST::FunctionExpression *ast) override; bool visit(AST::IdentifierExpression *ast) override; bool visit(AST::NestedExpression *ast) override; @@ -397,13 +619,14 @@ protected: bool visit(AST::NotExpression *ast) override; bool visit(AST::NullExpression *ast) override; bool visit(AST::NumericLiteral *ast) override; - bool visit(AST::ObjectLiteral *ast) override; + bool visit(AST::ObjectPattern *ast) override; bool visit(AST::PostDecrementExpression *ast) override; bool visit(AST::PostIncrementExpression *ast) override; bool visit(AST::PreDecrementExpression *ast) override; bool visit(AST::PreIncrementExpression *ast) override; bool visit(AST::RegExpLiteral *ast) override; bool visit(AST::StringLiteral *ast) override; + bool visit(AST::TemplateLiteral *ast) override; bool visit(AST::ThisExpression *ast) override; bool visit(AST::TildeExpression *ast) override; bool visit(AST::TrueLiteral *ast) override; @@ -412,10 +635,9 @@ protected: bool visit(AST::UnaryPlusExpression *ast) override; bool visit(AST::VoidExpression *ast) override; bool visit(AST::FunctionDeclaration *ast) override; - - // source elements - bool visit(AST::FunctionSourceElement *ast) override; - bool visit(AST::StatementSourceElement *ast) override; + bool visit(AST::YieldExpression *ast) override; + bool visit(AST::ClassExpression *ast) override; + bool visit(AST::ClassDeclaration *ast) override; // statements bool visit(AST::Block *ast) override; @@ -429,8 +651,6 @@ protected: bool visit(AST::ForStatement *ast) override; bool visit(AST::IfStatement *ast) override; bool visit(AST::LabelledStatement *ast) override; - bool visit(AST::LocalForEachStatement *ast) override; - bool visit(AST::LocalForStatement *ast) override; bool visit(AST::ReturnStatement *ast) override; bool visit(AST::SwitchStatement *ast) override; bool visit(AST::ThrowStatement *ast) override; @@ -447,7 +667,7 @@ protected: bool visit(AST::UiScriptBinding *ast) override; bool visit(AST::UiSourceElement *ast) override; - bool throwSyntaxErrorOnEvalOrArgumentsInStrictMode(QV4::IR::Expr* expr, const AST::SourceLocation &loc); + bool throwSyntaxErrorOnEvalOrArgumentsInStrictMode(const Reference &r, const AST::SourceLocation &loc); virtual void throwSyntaxError(const AST::SourceLocation &loc, const QString &detail); virtual void throwReferenceError(const AST::SourceLocation &loc, const QString &detail); @@ -457,117 +677,129 @@ public: QList<QQmlError> qmlErrors() const; #endif -protected: - Result _expr; - QString _property; - UiMember _uiMember; - QV4::IR::Module *_module; - QV4::IR::Function *_function; - QV4::IR::BasicBlock *_block; - QV4::IR::BasicBlock *_exitBlock; - unsigned _returnAddress; - Environment *_variableEnvironment; - Loop *_loop; - AST::LabelledStatement *_labelledStatement; - ScopeAndFinally *_scopeAndFinally; - QHash<AST::Node *, Environment *> _envMap; - QHash<AST::FunctionExpression *, int> _functionMap; - QStack<QV4::IR::BasicBlock *> _exceptionHandlers; - bool _strictMode; + Reference binopHelper(QSOperator::Op oper, Reference &left, Reference &right); + Reference jumpBinop(QSOperator::Op oper, Reference &left, Reference &right); + struct Arguments { int argc; int argv; bool hasSpread; }; + Arguments pushArgs(AST::ArgumentList *args); + void handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject); - bool _fileNameIsUrl; - bool hasError; - QList<QQmlJS::DiagnosticMessage> _errors; + Arguments pushTemplateArgs(AST::TemplateLiteral *args); + void createTemplateObject(AST::TemplateLiteral *t); - class ScanFunctions: protected Visitor - { - typedef QV4::TemporaryAssignment<bool> TemporaryBoolAssignment; - public: - ScanFunctions(Codegen *cg, const QString &sourceCode, CompilationMode defaultProgramMode); - void operator()(AST::Node *node); + void setUseFastLookups(bool b) { useFastLookups = b; } - void enterEnvironment(AST::Node *node, CompilationMode compilationMode); - void leaveEnvironment(); + void handleTryCatch(AST::TryStatement *ast); + void handleTryFinally(AST::TryStatement *ast); - void enterQmlScope(AST::Node *ast, const QString &name) - { enterFunction(ast, name, /*formals*/0, /*body*/0, /*expr*/0, /*isExpression*/false); } - void enterQmlFunction(AST::FunctionDeclaration *ast) - { enterFunction(ast, false, false); } + Reference referenceForName(const QString &name, bool lhs, const QQmlJS::AST::SourceLocation &accessLocation = QQmlJS::AST::SourceLocation()); - protected: - using Visitor::visit; - using Visitor::endVisit; + QQmlRefPointer<QV4::CompiledData::CompilationUnit> generateCompilationUnit(bool generateUnitData = true); + static QQmlRefPointer<QV4::CompiledData::CompilationUnit> createUnitForLoading(); - void checkDirectivePrologue(AST::SourceElements *ast); + Context *currentContext() const { return _context; } + BytecodeGenerator *generator() const { return bytecodeGenerator; } - void checkName(const QStringRef &name, const AST::SourceLocation &loc); - void checkForArguments(AST::FormalParameterList *parameters); + void loadClosure(int index); - bool visit(AST::Program *ast) override; - void endVisit(AST::Program *) override; + Module *module() const { return _module; } - bool visit(AST::CallExpression *ast) override; - bool visit(AST::NewMemberExpression *ast) override; - bool visit(AST::ArrayLiteral *ast) override; - bool visit(AST::VariableDeclaration *ast) override; - bool visit(AST::IdentifierExpression *ast) override; - bool visit(AST::ExpressionStatement *ast) override; - bool visit(AST::FunctionExpression *ast) override; + BytecodeGenerator::Label returnLabel() { + if (!_returnLabel) + _returnLabel = new BytecodeGenerator::Label(bytecodeGenerator->newLabel()); + return *_returnLabel; + } - void enterFunction(AST::FunctionExpression *ast, bool enterName, bool isExpression = true); + void setGlobalNames(const QSet<QString>& globalNames) { + m_globalNames = globalNames; + } - void endVisit(AST::FunctionExpression *) override; - bool visit(AST::ObjectLiteral *ast) override; +protected: + friend class ScanFunctions; + friend struct ControlFlow; + friend struct ControlFlowCatch; + friend struct ControlFlowFinally; + Result _expr; + VolatileMemoryLocations _volatileMemoryLocations; + Module *_module; + int _returnAddress; + Context *_context; + Context *_functionContext = nullptr; + AST::LabelledStatement *_labelledStatement; + QV4::Compiler::JSUnitGenerator *jsUnitGenerator; + BytecodeGenerator *bytecodeGenerator = nullptr; + Moth::BytecodeGenerator::Label *_returnLabel = nullptr; + bool _strictMode; + bool useFastLookups = true; + bool requiresReturnValue = false; + bool insideSwitch = false; + bool inFormalParameterList = false; + bool functionEndsWithReturn = false; + bool _tailCallsAreAllowed = true; + QSet<QString> m_globalNames; - bool visit(AST::PropertyGetterSetter *ast) override; - void endVisit(AST::PropertyGetterSetter *) override; + ControlFlow *controlFlow = nullptr; - bool visit(AST::FunctionDeclaration *ast) override; - void endVisit(AST::FunctionDeclaration *) override; + bool _fileNameIsUrl; + bool hasError; + QList<QQmlJS::DiagnosticMessage> _errors; - bool visit(AST::WithStatement *ast) override; + class TailCallBlocker + { + public: + TailCallBlocker(Codegen *cg, bool onoff = false) + : _cg(cg) + , _saved(_cg->_tailCallsAreAllowed) + , _onoff(onoff) + { _cg->_tailCallsAreAllowed = onoff; } - bool visit(AST::DoWhileStatement *ast) override; - bool visit(AST::ForStatement *ast) override; - bool visit(AST::LocalForStatement *ast) override; - bool visit(AST::ForEachStatement *ast) override; - bool visit(AST::LocalForEachStatement *ast) override; - bool visit(AST::ThisExpression *ast) override; + ~TailCallBlocker() + { _cg->_tailCallsAreAllowed = _saved; } - bool visit(AST::Block *ast) override; + void unblock() const + { _cg->_tailCallsAreAllowed = _saved; } - protected: - void enterFunction(AST::Node *ast, const QString &name, AST::FormalParameterList *formals, AST::FunctionBody *body, AST::FunctionExpression *expr, bool isExpression); + void reblock() const + { _cg->_tailCallsAreAllowed = _onoff; } - // fields: + private: Codegen *_cg; - const QString _sourceCode; - Environment *_variableEnvironment; - QStack<Environment *> _envStack; - - bool _allowFuncDecls; - CompilationMode defaultProgramMode; + bool _saved; + bool _onoff; }; -}; + class RecursionDepthCheck { + public: + RecursionDepthCheck(Codegen *cg, const AST::SourceLocation &loc) + : _cg(cg) + { +#ifdef QT_NO_DEBUG + const int depthLimit = 4000; // limit to ~1000 deep +#else + const int depthLimit = 1000; // limit to ~250 deep +#endif // QT_NO_DEBUG + + ++_cg->_recursionDepth; + if (_cg->_recursionDepth > depthLimit) + _cg->throwSyntaxError(loc, QStringLiteral("Maximum statement or expression depth exceeded")); + } -#ifndef V4_BOOTSTRAP -class RuntimeCodegen : public Codegen -{ -public: - RuntimeCodegen(QV4::ExecutionEngine *engine, bool strict) - : Codegen(strict) - , engine(engine) - {} + ~RecursionDepthCheck() + { --_cg->_recursionDepth; } + + private: + Codegen *_cg; + }; + int _recursionDepth = 0; + friend class RecursionDepthCheck; - void throwSyntaxError(const AST::SourceLocation &loc, const QString &detail) override; - void throwReferenceError(const AST::SourceLocation &loc, const QString &detail) override; private: - QV4::ExecutionEngine *engine; + VolatileMemoryLocations scanVolatileMemoryLocations(AST::Node *ast) const; + void handleConstruct(const Reference &base, AST::ArgumentList *args); }; -#endif // V4_BOOTSTRAP + +} } diff --git a/src/qml/compiler/qv4compilationunitmapper.cpp b/src/qml/compiler/qv4compilationunitmapper.cpp index d94f7ac238..350f6f9485 100644 --- a/src/qml/compiler/qv4compilationunitmapper.cpp +++ b/src/qml/compiler/qv4compilationunitmapper.cpp @@ -59,36 +59,4 @@ CompilationUnitMapper::~CompilationUnitMapper() close(); } -bool CompilationUnitMapper::verifyHeader(const CompiledData::Unit *header, QDateTime sourceTimeStamp, 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; - } - - if (header->sourceTimeStamp) { - // 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 index b24f98df7c..80f914c141 100644 --- a/src/qml/compiler/qv4compilationunitmapper_p.h +++ b/src/qml/compiler/qv4compilationunitmapper_p.h @@ -72,8 +72,6 @@ public: void close(); private: - static bool verifyHeader(const QV4::CompiledData::Unit *header, QDateTime sourceTimeStamp, QString *errorString); - #if defined(Q_OS_UNIX) size_t length; #endif diff --git a/src/qml/compiler/qv4compilationunitmapper_unix.cpp b/src/qml/compiler/qv4compilationunitmapper_unix.cpp index 38dabc41cf..6768bc9596 100644 --- a/src/qml/compiler/qv4compilationunitmapper_unix.cpp +++ b/src/qml/compiler/qv4compilationunitmapper_unix.cpp @@ -42,7 +42,7 @@ #include <sys/mman.h> #include <functional> #include <private/qcore_unix_p.h> -#include <private/qdeferredcleanup_p.h> +#include <QScopeGuard> #include <QDateTime> #include "qv4compileddata_p.h" @@ -61,7 +61,7 @@ CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, co return nullptr; } - QDeferredCleanup cleanup([fd]{ + auto cleanup = qScopeGuard([fd]{ qt_safe_close(fd) ; }); @@ -73,7 +73,7 @@ CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, co return nullptr; } - if (!verifyHeader(&header, sourceTimeStamp, errorString)) + if (!header.verifyHeader(sourceTimeStamp, errorString)) return nullptr; // Data structure and qt version matched, so now we can access the rest of the file safely. @@ -92,8 +92,16 @@ CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, co void CompilationUnitMapper::close() { - if (dataPtr != nullptr) - munmap(dataPtr, length); + // Do not unmap the data here. + if (dataPtr != nullptr) { + // Do not unmap cache files that are built with the StaticData flag. That's the majority of + // them and it's necessary to benefit from the QString literal optimization. There might + // still be QString instances around that point into that memory area. The memory is backed + // on the disk, so the kernel is free to release the pages and all that remains is the + // address space allocation. + if (!(reinterpret_cast<CompiledData::Unit*>(dataPtr)->flags & CompiledData::Unit::StaticData)) + munmap(dataPtr, length); + } dataPtr = nullptr; } diff --git a/src/qml/compiler/qv4compilationunitmapper_win.cpp b/src/qml/compiler/qv4compilationunitmapper_win.cpp index d7a93ae233..779c1288fe 100644 --- a/src/qml/compiler/qv4compilationunitmapper_win.cpp +++ b/src/qml/compiler/qv4compilationunitmapper_win.cpp @@ -40,7 +40,7 @@ #include "qv4compilationunitmapper_p.h" #include "qv4compileddata_p.h" -#include <private/qdeferredcleanup_p.h> +#include <QScopeGuard> #include <QFileInfo> #include <QDateTime> #include <qt_windows.h> @@ -71,7 +71,7 @@ CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, co return nullptr; } - QDeferredCleanup fileHandleCleanup([handle]{ + auto fileHandleCleanup = qScopeGuard([handle]{ CloseHandle(handle); }); @@ -87,27 +87,22 @@ CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, co return nullptr; } - if (!verifyHeader(&header, sourceTimeStamp, errorString)) + if (!header.verifyHeader(sourceTimeStamp, 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); + HANDLE fileMappingHandle = CreateFileMapping(handle, 0, PAGE_READONLY, 0, 0, 0); if (!fileMappingHandle) { *errorString = qt_error_string(GetLastError()); return nullptr; } - QDeferredCleanup mappingCleanup([fileMappingHandle]{ + auto mappingCleanup = qScopeGuard([fileMappingHandle]{ CloseHandle(fileMappingHandle); }); - dataPtr = MapViewOfFile(fileMappingHandle, viewFlags, 0, 0, 0); + dataPtr = MapViewOfFile(fileMappingHandle, FILE_MAP_READ, 0, 0, 0); if (!dataPtr) { *errorString = qt_error_string(GetLastError()); return nullptr; @@ -118,8 +113,15 @@ CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, co void CompilationUnitMapper::close() { - if (dataPtr != nullptr) - UnmapViewOfFile(dataPtr); + if (dataPtr != nullptr) { + // Do not unmap cache files that are built with the StaticData flag. That's the majority of + // them and it's necessary to benefit from the QString literal optimization. There might + // still be QString instances around that point into that memory area. The memory is backed + // on the disk, so the kernel is free to release the pages and all that remains is the + // address space allocation. + if (!(reinterpret_cast<CompiledData::Unit*>(dataPtr)->flags & CompiledData::Unit::StaticData)) + UnmapViewOfFile(dataPtr); + } dataPtr = nullptr; } diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index a0dd4c426c..5dd6fca023 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "qv4compileddata_p.h" -#include "qv4jsir_p.h" #include <private/qv4value_p.h> #ifndef V4_BOOTSTRAP #include <private/qv4engine_p.h> @@ -46,9 +45,11 @@ #include <private/qv4objectproto_p.h> #include <private/qv4lookup_p.h> #include <private/qv4regexpobject_p.h> -#include <private/qqmlpropertycache_p.h> +#include <private/qv4regexp_p.h> #include <private/qqmltypeloader_p.h> #include <private/qqmlengine_p.h> +#include <private/qv4vme_moth_p.h> +#include <private/qv4module_p.h> #include "qv4compilationunitmapper_p.h" #include <QQmlPropertyMap> #include <QDateTime> @@ -57,19 +58,18 @@ #include <QScopedValueRollback> #include <QStandardPaths> #include <QDir> +#include <private/qv4identifiertable_p.h> #endif #include <private/qqmlirbuilder_p.h> #include <QCoreApplication> #include <QCryptographicHash> #include <QSaveFile> +#include <QScopeGuard> -#include <algorithm> +// generated by qmake: +#include "qml_compile_hash_p.h" -#if defined(QT_BUILD_INTERNAL) -#if defined(Q_OS_UNIX) && !defined(QT_NO_DYNAMIC_CAST) -#include <dlfcn.h> -#endif -#endif +#include <algorithm> QT_BEGIN_NAMESPACE @@ -77,42 +77,56 @@ namespace QV4 { namespace CompiledData { -#if !defined(V4_BOOTSTRAP) -static QString cacheFilePath(const QUrl &url) +#if defined(QML_COMPILE_HASH) +# ifdef Q_OS_LINUX +// Place on a separate section on Linux so it's easier to check from outside +// what the hash version is. +__attribute__((section(".qml_compile_hash"))) +# endif +const char qml_compile_hash[48 + 1] = QML_COMPILE_HASH; +static_assert(sizeof(Unit::libraryVersionHash) >= QML_COMPILE_HASH_LENGTH + 1, "Compile hash length exceeds reserved size in data structure. Please adjust and bump the format version"); +#else +# error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files" +#endif + + +CompilationUnit::CompilationUnit(const Unit *unitData, const QString &fileName, const QString &finalUrlString) { - 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(); + setUnitData(unitData, nullptr, fileName, finalUrlString); } -#endif #ifndef V4_BOOTSTRAP -CompilationUnit::CompilationUnit() - : data(0) - , engine(0) - , 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; + + if (data) { + if (data->qmlUnit() != qmlData) + free(const_cast<QmlUnit *>(qmlData)); + qmlData = nullptr; + + if (!(data->flags & QV4::CompiledData::Unit::StaticData)) + free(const_cast<Unit *>(data)); + } + data = nullptr; +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + delete [] constants; + constants = nullptr; +#endif + + delete [] imports; + imports = nullptr; +} + +QString CompilationUnit::localCacheFilePath(const QUrl &url) +{ + const QString localSourcePath = QQmlFile::urlToLocalFileOrQrc(url); + const QString cacheFileSuffix = QFileInfo(localSourcePath + QLatin1Char('c')).completeSuffix(); + 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('.') + cacheFileSuffix; } QV4::Function *CompilationUnit::linkToEngine(ExecutionEngine *engine) @@ -122,26 +136,21 @@ QV4::Function *CompilationUnit::linkToEngine(ExecutionEngine *engine) Q_ASSERT(!runtimeStrings); Q_ASSERT(data); - runtimeStrings = (QV4::Heap::String **)malloc(data->stringTableSize * sizeof(QV4::Heap::String*)); + const quint32 stringCount = totalStringCount(); + runtimeStrings = (QV4::Heap::String **)malloc(stringCount * sizeof(QV4::Heap::String*)); // memset the strings to 0 in case a GC run happens while we're within the loop below - memset(runtimeStrings, 0, data->stringTableSize * sizeof(QV4::Heap::String*)); - for (uint i = 0; i < data->stringTableSize; ++i) - runtimeStrings[i] = engine->newIdentifier(data->stringAt(i)); + memset(runtimeStrings, 0, stringCount * sizeof(QV4::Heap::String*)); + for (uint i = 0; i < stringCount; ++i) + runtimeStrings[i] = engine->newString(stringAt(i)); runtimeRegularExpressions = new QV4::Value[data->regexpTableSize]; // memset the regexps to 0 in case a GC run happens while we're within the loop below memset(runtimeRegularExpressions, 0, data->regexpTableSize * sizeof(QV4::Value)); for (uint i = 0; i < data->regexpTableSize; ++i) { const CompiledData::RegExp *re = data->regexpAt(i); - int flags = 0; - if (re->flags & CompiledData::RegExp::RegExp_Global) - flags |= IR::RegExp::RegExp_Global; - if (re->flags & CompiledData::RegExp::RegExp_IgnoreCase) - flags |= IR::RegExp::RegExp_IgnoreCase; - if (re->flags & CompiledData::RegExp::RegExp_Multiline) - flags |= IR::RegExp::RegExp_Multiline; - QV4::Heap::RegExpObject *ro = engine->newRegExpObject(data->stringAt(re->stringIndex), flags); - runtimeRegularExpressions[i] = ro; + uint f = re->flags; + const CompiledData::RegExp::Flags flags = static_cast<CompiledData::RegExp::Flags>(f); + runtimeRegularExpressions[i] = QV4::RegExp::create(engine, stringAt(re->stringIndex), flags); } if (data->lookupTableSize) { @@ -158,66 +167,108 @@ QV4::Function *CompilationUnit::linkToEngine(ExecutionEngine *engine) l->setter = QV4::Lookup::setterGeneric; else if (type == CompiledData::Lookup::Type_GlobalGetter) l->globalGetter = QV4::Lookup::globalGetterGeneric; - else if (type == CompiledData::Lookup::Type_IndexedGetter) - l->indexedGetter = QV4::Lookup::indexedGetterGeneric; - else if (type == CompiledData::Lookup::Type_IndexedSetter) - l->indexedSetter = QV4::Lookup::indexedSetterGeneric; - - for (int j = 0; j < QV4::Lookup::Size; ++j) - l->classList[j] = 0; - l->level = -1; - l->index = UINT_MAX; l->nameIndex = compiledLookups[i].nameIndex; } } if (data->jsClassTableSize) { - runtimeClasses = (QV4::InternalClass**)malloc(data->jsClassTableSize * sizeof(QV4::InternalClass*)); + runtimeClasses = (QV4::Heap::InternalClass **)malloc(data->jsClassTableSize * sizeof(QV4::Heap::InternalClass *)); + // memset the regexps to 0 in case a GC run happens while we're within the loop below + memset(runtimeClasses, 0, data->jsClassTableSize * sizeof(QV4::Heap::InternalClass *)); for (uint i = 0; i < data->jsClassTableSize; ++i) { int memberCount = 0; const CompiledData::JSClassMember *member = data->jsClassAt(i, &memberCount); - QV4::InternalClass *klass = engine->internalClasses[QV4::ExecutionEngine::Class_Object]; + runtimeClasses[i] = engine->internalClasses(QV4::ExecutionEngine::Class_Object); for (int j = 0; j < memberCount; ++j, ++member) - klass = klass->addMember(runtimeStrings[member->nameOffset]->identifier, member->isAccessor ? QV4::Attr_Accessor : QV4::Attr_Data); - - runtimeClasses[i] = klass; + runtimeClasses[i] = runtimeClasses[i]->addMember(engine->identifierTable->asPropertyKey(runtimeStrings[member->nameOffset]), member->isAccessor ? QV4::Attr_Accessor : QV4::Attr_Data); } } -#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 + runtimeFunctions.resize(data->functionTableSize); + for (int i = 0 ;i < runtimeFunctions.size(); ++i) { + const QV4::CompiledData::Function *compiledFunction = data->functionAt(i); + runtimeFunctions[i] = QV4::Function::create(engine, this, compiledFunction); + } + + Scope scope(engine); + Scoped<InternalClass> ic(scope); + + runtimeBlocks.resize(data->blockTableSize); + for (int i = 0 ;i < runtimeBlocks.size(); ++i) { + const QV4::CompiledData::Block *compiledBlock = data->blockAt(i); + ic = engine->internalClasses(EngineBase::Class_CallContext); - linkBackendToEngine(engine); + // first locals + const quint32_le *localsIndices = compiledBlock->localsTable(); + for (quint32 j = 0; j < compiledBlock->nLocals; ++j) + ic = ic->addMember(engine->identifierTable->asPropertyKey(runtimeStrings[localsIndices[j]]), Attr_NotConfigurable); + runtimeBlocks[i] = ic->d(); + } + + static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); + if (showCode) { + qDebug() << "=== Constant table"; + Moth::dumpConstantTable(constants, data->constantTableSize); + qDebug() << "=== String table"; + for (uint i = 0, end = totalStringCount(); i < end; ++i) + qDebug() << " " << i << ":" << runtimeStrings[i]->toQString(); + qDebug() << "=== Closure table"; + for (uint i = 0; i < data->functionTableSize; ++i) + qDebug() << " " << i << ":" << runtimeFunctions[i]->name()->toQString(); + qDebug() << "root function at index " << (data->indexOfRootFunction != -1 ? data->indexOfRootFunction : 0); + } if (data->indexOfRootFunction != -1) return runtimeFunctions[data->indexOfRootFunction]; else - return 0; + return nullptr; +} + +Heap::Object *CompilationUnit::templateObjectAt(int index) const +{ + Q_ASSERT(index < int(data->templateObjectTableSize)); + if (!templateObjects.size()) + templateObjects.resize(data->templateObjectTableSize); + Heap::Object *o = templateObjects.at(index); + if (o) + return o; + + // create the template object + Scope scope(engine); + const CompiledData::TemplateObject *t = data->templateObjectAt(index); + Scoped<ArrayObject> a(scope, engine->newArrayObject(t->size)); + Scoped<ArrayObject> raw(scope, engine->newArrayObject(t->size)); + ScopedValue s(scope); + for (uint i = 0; i < t->size; ++i) { + s = runtimeStrings[t->stringIndexAt(i)]; + a->arraySet(i, s); + s = runtimeStrings[t->rawStringIndexAt(i)]; + raw->arraySet(i, s); + } + + ObjectPrototype::method_freeze(engine->functionCtor(), nullptr, raw, 1); + a->defineReadonlyProperty(QStringLiteral("raw"), raw); + ObjectPrototype::method_freeze(engine->functionCtor(), nullptr, a, 1); + + templateObjects[index] = a->objectValue()->d(); + return templateObjects.at(index); } void CompilationUnit::unlink() { if (engine) - engine->compilationUnits.erase(engine->compilationUnits.find(this)); + nextCompilationUnit.remove(); 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); + Q_ASSERT(data && propertyCaches.count() > 0 && propertyCaches.at(/*root object*/0)); + if (qmlEngine) + qmlEngine->unregisterInternalCompositeType(this); + QQmlMetaType::unregisterInternalCompositeType(this); isRegisteredWithEngine = false; } propertyCaches.clear(); - for (int ii = 0; ii < dependentScripts.count(); ++ii) - dependentScripts.at(ii)->release(); dependentScripts.clear(); typeNameCache = nullptr; @@ -225,75 +276,87 @@ void CompilationUnit::unlink() qDeleteAll(resolvedTypes); resolvedTypes.clear(); - engine = 0; + engine = nullptr; + qmlEngine = nullptr; free(runtimeStrings); - runtimeStrings = 0; + runtimeStrings = nullptr; delete [] runtimeLookups; - runtimeLookups = 0; + runtimeLookups = nullptr; delete [] runtimeRegularExpressions; - runtimeRegularExpressions = 0; + runtimeRegularExpressions = nullptr; free(runtimeClasses); - runtimeClasses = 0; - qDeleteAll(runtimeFunctions); + runtimeClasses = nullptr; + for (QV4::Function *f : qAsConst(runtimeFunctions)) + f->destroy(); runtimeFunctions.clear(); -#if Q_BYTE_ORDER == Q_BIG_ENDIAN - delete [] constants; -#endif } void CompilationUnit::markObjects(QV4::MarkStack *markStack) { - for (uint i = 0; i < data->stringTableSize; ++i) - if (runtimeStrings[i]) - runtimeStrings[i]->mark(markStack); + if (runtimeStrings) { + for (uint i = 0, end = totalStringCount(); i < end; ++i) + if (runtimeStrings[i]) + runtimeStrings[i]->mark(markStack); + } if (runtimeRegularExpressions) { for (uint i = 0; i < data->regexpTableSize; ++i) runtimeRegularExpressions[i].mark(markStack); } -} + if (runtimeClasses) { + for (uint i = 0; i < data->jsClassTableSize; ++i) + if (runtimeClasses[i]) + runtimeClasses[i]->mark(markStack); + } + for (QV4::Function *f : qAsConst(runtimeFunctions)) + if (f && f->internalClass) + f->internalClass->mark(markStack); + for (QV4::Heap::InternalClass *c : qAsConst(runtimeBlocks)) + if (c) + c->mark(markStack); + + for (QV4::Heap::Object *o : qAsConst(templateObjects)) + if (o) + o->mark(markStack); + + if (runtimeLookups) { + for (uint i = 0; i < data->lookupTableSize; ++i) + runtimeLookups[i].markObjects(markStack); + } -void CompilationUnit::destroy() -{ - QQmlEngine *qmlEngine = 0; - if (engine && engine->v8Engine) - qmlEngine = engine->v8Engine->engine(); - if (qmlEngine) - QQmlEnginePrivate::deleteInEngineThread(qmlEngine, this); - else - delete this; + if (m_module) + m_module->mark(markStack); } -IdentifierHash<int> CompilationUnit::namedObjectsPerComponent(int componentObjectIndex) +IdentifierHash CompilationUnit::createNamedObjectsPerComponent(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); + IdentifierHash namedObjectCache(engine); + const CompiledData::Object *component = objectAt(componentObjectIndex); + const quint32_le *namedObjectIndexPtr = component->namedObjectsInComponentTable(); + for (quint32 i = 0; i < component->nNamedObjectsInComponent; ++i, ++namedObjectIndexPtr) { + const CompiledData::Object *namedObject = objectAt(*namedObjectIndexPtr); + namedObjectCache.add(runtimeStrings[namedObject->idNameIndex], namedObject->id); } - return *it; + return *namedObjectsPerComponentCache.insert(componentObjectIndex, namedObjectCache); } -void CompilationUnit::finalize(QQmlEnginePrivate *engine) +void CompilationUnit::finalizeCompositeType(QQmlEnginePrivate *qmlEngine) { + this->qmlEngine = qmlEngine; + // Add to type registry of composites - if (propertyCaches.needsVMEMetaObject(data->indexOfRootObject)) - engine->registerInternalCompositeType(this); - else { - const QV4::CompiledData::Object *obj = objectAt(data->indexOfRootObject); + if (propertyCaches.needsVMEMetaObject(/*root object*/0)) { + QQmlMetaType::registerInternalCompositeType(this); + qmlEngine->registerInternalCompositeType(this); + } else { + const QV4::CompiledData::Object *obj = objectAt(/*root object*/0); 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(); + metaTypeId = typeRef->type.typeId(); + listMetaTypeId = typeRef->type.qListTypeId(); } } @@ -301,12 +364,12 @@ void CompilationUnit::finalize(QQmlEnginePrivate *engine) 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); + for (quint32 i = 0, count = this->objectCount(); i < count; ++i) { + const QV4::CompiledData::Object *obj = objectAt(i); bindingCount += obj->nBindings; if (auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex)) { - if (QQmlType *qmlType = typeRef->type) { - if (qmlType->parserStatusCast() != -1) + if (typeRef->type.isValid()) { + if (typeRef->type.parserStatusCast() != -1) ++parserStatusCount; } ++objectCount; @@ -341,58 +404,249 @@ bool CompilationUnit::verifyChecksum(const DependentTypesHasher &dependencyHashe sizeof(data->dependencyMD5Checksum)) == 0; } -bool CompilationUnit::loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, EvalISelFactory *iselFactory, QString *errorString) +QStringList CompilationUnit::moduleRequests() const { - if (!QQmlFile::isLocalFile(url)) { - *errorString = QStringLiteral("File has to be a local file."); - return false; - } + QStringList requests; + requests.reserve(data->moduleRequestTableSize); + for (uint i = 0; i < data->moduleRequestTableSize; ++i) + requests << stringAt(data->moduleRequestTable()[i]); + return requests; +} - const QString sourcePath = QQmlFile::urlToLocalFileOrQrc(url); - QScopedPointer<CompilationUnitMapper> cacheFile(new CompilationUnitMapper()); +Heap::Module *CompilationUnit::instantiate(ExecutionEngine *engine) +{ + if (isESModule() && m_module) + return m_module; - CompiledData::Unit *mappedUnit = cacheFile->open(cacheFilePath(url), sourceTimeStamp, errorString); - if (!mappedUnit) - return false; + if (data->indexOfRootFunction < 0) + return nullptr; - const Unit * const oldDataPtr = (data && !(data->flags & QV4::CompiledData::Unit::StaticData)) ? data : nullptr; - QScopedValueRollback<const Unit *> dataPtrChange(data, mappedUnit); + if (!this->engine) + linkToEngine(engine); - if (data->sourceFileIndex != 0 && sourcePath != QQmlFile::urlToLocalFileOrQrc(stringAt(data->sourceFileIndex))) { - *errorString = QStringLiteral("QML source file has moved to a different location."); - return false; + Scope scope(engine); + Scoped<Module> module(scope, engine->memoryManager->allocate<Module>(engine, this)); + + if (isESModule()) + m_module = module->d(); + + for (const QString &request: moduleRequests()) { + auto dependentModuleUnit = engine->loadModule(QUrl(request), this); + if (engine->hasException) + return nullptr; + dependentModuleUnit->instantiate(engine); } - { - 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; + ScopedString importName(scope); + + const uint importCount = data->importEntryTableSize; + imports = new const Value *[importCount]; + memset(imports, 0, importCount * sizeof(Value *)); + for (uint i = 0; i < importCount; ++i) { + const CompiledData::ImportEntry &entry = data->importEntryTable()[i]; + auto dependentModuleUnit = engine->loadModule(QUrl(stringAt(entry.moduleRequest)), this); + importName = runtimeStrings[entry.importName]; + const Value *valuePtr = dependentModuleUnit->resolveExport(importName); + if (!valuePtr) { + QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference "); + referenceErrorMessage += importName->toQString(); + engine->throwReferenceError(referenceErrorMessage, fileName(), entry.location.line, entry.location.column); + return nullptr; } + imports[i] = valuePtr; } - { - 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; + for (uint i = 0; i < data->indirectExportEntryTableSize; ++i) { + const CompiledData::ExportEntry &entry = data->indirectExportEntryTable()[i]; + auto dependentModuleUnit = engine->loadModule(QUrl(stringAt(entry.moduleRequest)), this); + if (!dependentModuleUnit) + return nullptr; + + ScopedString importName(scope, runtimeStrings[entry.importName]); + if (!dependentModuleUnit->resolveExport(importName)) { + QString referenceErrorMessage = QStringLiteral("Unable to resolve re-export reference "); + referenceErrorMessage += importName->toQString(); + engine->throwReferenceError(referenceErrorMessage, fileName(), entry.location.line, entry.location.column); + return nullptr; } } - if (!memoryMapCode(errorString)) - return false; + return module->d(); +} - dataPtrChange.commit(); - free(const_cast<Unit*>(oldDataPtr)); - backingFile.reset(cacheFile.take()); - return true; +const Value *CompilationUnit::resolveExport(QV4::String *exportName) +{ + QVector<ResolveSetEntry> resolveSet; + return resolveExportRecursively(exportName, &resolveSet); +} + +QStringList CompilationUnit::exportedNames() const +{ + QStringList names; + QVector<const CompiledData::CompilationUnit*> exportNameSet; + getExportedNamesRecursively(&names, &exportNameSet); + names.sort(); + auto last = std::unique(names.begin(), names.end()); + names.erase(last, names.end()); + return names; +} + +const Value *CompilationUnit::resolveExportRecursively(QV4::String *exportName, QVector<ResolveSetEntry> *resolveSet) +{ + if (!m_module) + return nullptr; + + for (const auto &entry: *resolveSet) + if (entry.module == this && entry.exportName->isEqualTo(exportName)) + return nullptr; + + (*resolveSet) << ResolveSetEntry(this, exportName); + + if (exportName->toQString() == QLatin1String("*")) + return &m_module->self; + + Scope scope(engine); + + if (auto localExport = lookupNameInExportTable(data->localExportEntryTable(), data->localExportEntryTableSize, exportName)) { + ScopedString localName(scope, runtimeStrings[localExport->localName]); + uint index = m_module->scope->internalClass->indexOfValueOrGetter(localName->toPropertyKey()); + if (index == UINT_MAX) + return nullptr; + if (index >= m_module->scope->locals.size) + return imports[index - m_module->scope->locals.size]; + return &m_module->scope->locals[index]; + } + + if (auto indirectExport = lookupNameInExportTable(data->indirectExportEntryTable(), data->indirectExportEntryTableSize, exportName)) { + auto dependentModuleUnit = engine->loadModule(QUrl(stringAt(indirectExport->moduleRequest)), this); + if (!dependentModuleUnit) + return nullptr; + ScopedString importName(scope, runtimeStrings[indirectExport->importName]); + return dependentModuleUnit->resolveExportRecursively(importName, resolveSet); + } + + + if (exportName->toQString() == QLatin1String("default")) + return nullptr; + + const Value *starResolution = nullptr; + + for (uint i = 0; i < data->starExportEntryTableSize; ++i) { + const CompiledData::ExportEntry &entry = data->starExportEntryTable()[i]; + auto dependentModuleUnit = engine->loadModule(QUrl(stringAt(entry.moduleRequest)), this); + if (!dependentModuleUnit) + return nullptr; + + const Value *resolution = dependentModuleUnit->resolveExportRecursively(exportName, resolveSet); + // ### handle ambiguous + if (resolution) { + if (!starResolution) { + starResolution = resolution; + continue; + } + if (resolution != starResolution) + return nullptr; + } + } + + return starResolution; +} + +const ExportEntry *CompilationUnit::lookupNameInExportTable(const ExportEntry *firstExportEntry, int tableSize, QV4::String *name) const +{ + const CompiledData::ExportEntry *lastExportEntry = firstExportEntry + tableSize; + auto matchingExport = std::lower_bound(firstExportEntry, lastExportEntry, name, [this](const CompiledData::ExportEntry &lhs, QV4::String *name) { + return stringAt(lhs.exportName) < name->toQString(); + }); + if (matchingExport == lastExportEntry || stringAt(matchingExport->exportName) != name->toQString()) + return nullptr; + return matchingExport; +} + +void CompilationUnit::getExportedNamesRecursively(QStringList *names, QVector<const CompilationUnit*> *exportNameSet, bool includeDefaultExport) const +{ + if (exportNameSet->contains(this)) + return; + exportNameSet->append(this); + + const auto append = [names, includeDefaultExport](const QString &name) { + if (!includeDefaultExport && name == QLatin1String("default")) + return; + names->append(name); + }; + + for (uint i = 0; i < data->localExportEntryTableSize; ++i) { + const CompiledData::ExportEntry &entry = data->localExportEntryTable()[i]; + append(stringAt(entry.exportName)); + } + + for (uint i = 0; i < data->indirectExportEntryTableSize; ++i) { + const CompiledData::ExportEntry &entry = data->indirectExportEntryTable()[i]; + append(stringAt(entry.exportName)); + } + + for (uint i = 0; i < data->starExportEntryTableSize; ++i) { + const CompiledData::ExportEntry &entry = data->starExportEntryTable()[i]; + auto dependentModuleUnit = engine->loadModule(QUrl(stringAt(entry.moduleRequest)), this); + if (!dependentModuleUnit) + return; + dependentModuleUnit->getExportedNamesRecursively(names, exportNameSet, /*includeDefaultExport*/false); + } +} + +void CompilationUnit::evaluate() +{ + QV4::Scope scope(engine); + QV4::Scoped<Module> module(scope, m_module); + module->evaluate(); +} + +void CompilationUnit::evaluateModuleRequests() +{ + for (const QString &request: moduleRequests()) { + auto dependentModuleUnit = engine->loadModule(QUrl(request), this); + if (engine->hasException) + return; + dependentModuleUnit->evaluate(); + if (engine->hasException) + return; + } } -bool CompilationUnit::memoryMapCode(QString *errorString) +bool CompilationUnit::loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString) { - *errorString = QStringLiteral("Missing code mapping backend"); + if (!QQmlFile::isLocalFile(url)) { + *errorString = QStringLiteral("File has to be a local file."); + return false; + } + + const QString sourcePath = QQmlFile::urlToLocalFileOrQrc(url); + QScopedPointer<CompilationUnitMapper> cacheFile(new CompilationUnitMapper()); + + const QStringList cachePaths = { sourcePath + QLatin1Char('c'), localCacheFilePath(url) }; + for (const QString &cachePath : cachePaths) { + CompiledData::Unit *mappedUnit = cacheFile->open(cachePath, sourceTimeStamp, errorString); + if (!mappedUnit) + continue; + + const Unit * const oldDataPtr = (data && !(data->flags & QV4::CompiledData::Unit::StaticData)) ? data : nullptr; + const Unit *oldData = data; + auto dataPtrRevert = qScopeGuard([this, oldData](){ + setUnitData(oldData); + }); + setUnitData(mappedUnit); + + if (data->sourceFileIndex != 0 && sourcePath != QQmlFile::urlToLocalFileOrQrc(stringAt(data->sourceFileIndex))) { + *errorString = QStringLiteral("QML source file has moved to a different location."); + continue; + } + + dataPtrRevert.dismiss(); + free(const_cast<Unit*>(oldDataPtr)); + backingFile.reset(cacheFile.take()); + return true; + } + return false; } @@ -416,7 +670,7 @@ bool CompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString) *errorString = QStringLiteral("File has to be a local file."); return false; } - const QString outputFileName = cacheFilePath(unitUrl); + const QString outputFileName = localCacheFilePath(unitUrl); #endif #if QT_CONFIG(temporaryfile) @@ -435,17 +689,12 @@ bool CompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString) 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; @@ -459,152 +708,73 @@ bool CompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString) #endif // QT_CONFIG(temporaryfile) } -void CompilationUnit::prepareCodeOffsetsForDiskStorage(Unit *unit) +void CompilationUnit::setUnitData(const Unit *unitData, const QmlUnit *qmlUnit, + const QString &fileName, const QString &finalUrlString) { - 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; -} - -Unit *CompilationUnit::createUnitData(QmlIR::Document *irDocument) -{ - if (!irDocument->javaScriptCompilationUnit->data) - return irDocument->jsGenerator.generateUnit(QV4::Compiler::JSUnitGenerator::GenerateWithoutStringTable); - - QQmlRefPointer<QV4::CompiledData::CompilationUnit> compilationUnit = irDocument->javaScriptCompilationUnit; - QV4::CompiledData::Unit *jsUnit = const_cast<QV4::CompiledData::Unit*>(compilationUnit->data); - auto ensureWritableUnit = [&jsUnit, &compilationUnit]() { - if (jsUnit == compilationUnit->data) { - char *unitCopy = (char*)malloc(jsUnit->unitSize); - memcpy(unitCopy, jsUnit, jsUnit->unitSize); - jsUnit = reinterpret_cast<QV4::CompiledData::Unit*>(unitCopy); - } - }; - - QV4::Compiler::StringTableGenerator &stringTable = irDocument->jsGenerator.stringTable; - - if (jsUnit->sourceFileIndex == quint32(0) || jsUnit->stringAt(jsUnit->sourceFileIndex) != irDocument->jsModule.fileName) { - ensureWritableUnit(); - jsUnit->sourceFileIndex = stringTable.registerString(irDocument->jsModule.fileName); - } - - // Collect signals that have had a change in signature (from onClicked to onClicked(mouse) for example) - // and now need fixing in the QV4::CompiledData. Also register strings at the same time, to finalize - // the string table. - QVector<quint32> changedSignals; - QVector<QQmlJS::AST::FormalParameterList*> changedSignalParameters; - for (QmlIR::Object *o: qAsConst(irDocument->objects)) { - for (QmlIR::Binding *binding = o->firstBinding(); binding; binding = binding->next) { - if (!(binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression)) - continue; - - quint32 functionIndex = binding->value.compiledScriptIndex; - QmlIR::CompiledFunctionOrExpression *foe = o->functionsAndExpressions->slowAt(functionIndex); - if (!foe) - continue; - - // save absolute index - changedSignals << o->runtimeFunctionIndices.at(functionIndex); - - Q_ASSERT(foe->node); - Q_ASSERT(QQmlJS::AST::cast<QQmlJS::AST::FunctionDeclaration*>(foe->node)); - - QQmlJS::AST::FormalParameterList *parameters = QQmlJS::AST::cast<QQmlJS::AST::FunctionDeclaration*>(foe->node)->formals; - changedSignalParameters << parameters; - - for (; parameters; parameters = parameters->next) - stringTable.registerString(parameters->name.toString()); - } - } - - QVector<quint32> signalParameterNameTable; - quint32 signalParameterNameTableOffset = jsUnit->unitSize; - - // Update signal signatures - if (!changedSignals.isEmpty()) { - if (jsUnit == compilationUnit->data) { - char *unitCopy = (char*)malloc(jsUnit->unitSize); - memcpy(unitCopy, jsUnit, jsUnit->unitSize); - jsUnit = reinterpret_cast<QV4::CompiledData::Unit*>(unitCopy); - } - - for (int i = 0; i < changedSignals.count(); ++i) { - const uint functionIndex = changedSignals.at(i); - // The data is now read-write due to the copy above, so the const_cast is ok. - QV4::CompiledData::Function *function = const_cast<QV4::CompiledData::Function *>(jsUnit->functionAt(functionIndex)); - Q_ASSERT(function->nFormals == quint32(0)); - - function->formalsOffset = signalParameterNameTableOffset - jsUnit->functionOffsetTable()[functionIndex]; - - for (QQmlJS::AST::FormalParameterList *parameters = changedSignalParameters.at(i); - parameters; parameters = parameters->next) { - signalParameterNameTable.append(stringTable.getStringId(parameters->name.toString())); - function->nFormals = function->nFormals + 1; - } - - // Hack to ensure an activation is created. - function->flags |= QV4::CompiledData::Function::HasCatchOrWith | QV4::CompiledData::Function::HasDirectEval; - - signalParameterNameTableOffset += function->nFormals * sizeof(quint32); - } - } + data = unitData; + qmlData = nullptr; +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + delete [] constants; +#endif + constants = nullptr; + m_fileName.clear(); + m_finalUrlString.clear(); + if (!data) + return; - if (!signalParameterNameTable.isEmpty()) { - ensureWritableUnit(); - Q_ASSERT(jsUnit != compilationUnit->data); - const uint signalParameterTableSize = signalParameterNameTable.count() * sizeof(quint32); - uint newSize = jsUnit->unitSize + signalParameterTableSize; - const uint oldSize = jsUnit->unitSize; - char *unitWithSignalParameters = (char*)realloc(jsUnit, newSize); - memcpy(unitWithSignalParameters + oldSize, signalParameterNameTable.constData(), signalParameterTableSize); - jsUnit = reinterpret_cast<QV4::CompiledData::Unit*>(unitWithSignalParameters); - jsUnit->unitSize = newSize; - } + qmlData = qmlUnit ? qmlUnit : data->qmlUnit(); - if (jsUnit != compilationUnit->data) - jsUnit->flags &= ~QV4::CompiledData::Unit::StaticData; +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + Value *bigEndianConstants = new Value[data->constantTableSize]; + const quint64_le *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 - return jsUnit; + m_fileName = !fileName.isEmpty() ? fileName : stringAt(data->sourceFileIndex); + m_finalUrlString = !finalUrlString.isEmpty() ? finalUrlString : stringAt(data->finalUrlIndex); } -QString Binding::valueAsString(const Unit *unit) const +#ifndef V4_BOOTSTRAP +QString Binding::valueAsString(const CompilationUnit *unit) const { switch (type) { case Type_Script: case Type_String: return unit->stringAt(stringIndex); + case Type_Null: + return QStringLiteral("null"); case Type_Boolean: return value.b ? QStringLiteral("true") : QStringLiteral("false"); case Type_Number: - return QString::number(valueAsNumber()); + return QString::number(valueAsNumber(unit->constants)); case Type_Invalid: return QString(); #if !QT_CONFIG(translation) case Type_TranslationById: case Type_Translation: - return unit->stringAt(stringIndex); + return unit->stringAt(unit->unitData()->translations()[value.translationDataIndex].stringIndex); #else case Type_TranslationById: { - QByteArray id = unit->stringAt(stringIndex).toUtf8(); - return qtTrId(id.constData(), value.translationData.number); + const TranslationData &translation = unit->unitData()->translations()[value.translationDataIndex]; + QByteArray id = unit->stringAt(translation.stringIndex).toUtf8(); + return qtTrId(id.constData(), translation.number); } case Type_Translation: { + const TranslationData &translation = unit->unitData()->translations()[value.translationDataIndex]; // This code must match that in the qsTr() implementation - const QString &path = unit->stringAt(unit->sourceFileIndex); + const QString &path = unit->fileName(); int lastSlash = path.lastIndexOf(QLatin1Char('/')); QStringRef context = (lastSlash > -1) ? path.midRef(lastSlash + 1, path.length() - lastSlash - 5) : QStringRef(); QByteArray contextUtf8 = context.toUtf8(); - QByteArray comment = unit->stringAt(value.translationData.commentIndex).toUtf8(); - QByteArray text = unit->stringAt(stringIndex).toUtf8(); + QByteArray comment = unit->stringAt(translation.commentIndex).toUtf8(); + QByteArray text = unit->stringAt(translation.stringIndex).toUtf8(); return QCoreApplication::translate(contextUtf8.constData(), text.constData(), - comment.constData(), value.translationData.number); + comment.constData(), translation.number); } #endif default: @@ -656,7 +826,7 @@ QString Binding::escapedString(const QString &string) return tmp; } -QString Binding::valueAsScriptString(const Unit *unit) const +QString Binding::valueAsScriptString(const CompilationUnit *unit) const { if (type == Type_String) return escapedString(unit->stringAt(stringIndex)); @@ -664,13 +834,12 @@ 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 +QQmlRefPointer<QQmlPropertyCache> ResolvedTypeReference::propertyCache() const { - if (type) + if (type.isValid()) return typePropertyCache; else return compilationUnit->rootPropertyCache(); @@ -679,12 +848,12 @@ QQmlPropertyCache *ResolvedTypeReference::propertyCache() const /*! Returns the property cache, creating one if it doesn't already exist. The cache is not referenced. */ -QQmlPropertyCache *ResolvedTypeReference::createPropertyCache(QQmlEngine *engine) +QQmlRefPointer<QQmlPropertyCache> ResolvedTypeReference::createPropertyCache(QQmlEngine *engine) { if (typePropertyCache) { return typePropertyCache; - } else if (type) { - typePropertyCache = QQmlEnginePrivate::get(engine)->cache(type->metaObject()); + } else if (type.isValid()) { + typePropertyCache = QQmlEnginePrivate::get(engine)->cache(type.metaObject(), minorVersion); return typePropertyCache; } else { return compilationUnit->rootPropertyCache(); @@ -693,12 +862,12 @@ QQmlPropertyCache *ResolvedTypeReference::createPropertyCache(QQmlEngine *engine bool ResolvedTypeReference::addToHash(QCryptographicHash *hash, QQmlEngine *engine) { - if (type) { + if (type.isValid()) { bool ok = false; hash->addData(createPropertyCache(engine)->checksum(&ok)); return ok; } - hash->addData(compilationUnit->data->md5Checksum, sizeof(compilationUnit->data->md5Checksum)); + hash->addData(compilationUnit->unitData()->md5Checksum, sizeof(compilationUnit->unitData()->md5Checksum)); return true; } @@ -714,43 +883,16 @@ bool qtTypeInherits(const QMetaObject *mo) { void ResolvedTypeReference::doDynamicTypeCheck() { - const QMetaObject *mo = 0; + const QMetaObject *mo = nullptr; if (typePropertyCache) mo = typePropertyCache->firstCppMetaObject(); - else if (type) - mo = type->metaObject(); + else if (type.isValid()) + 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(QT_NO_DYNAMIC_CAST) && QT_CONFIG(dlopen) - 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::Md5); - 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) { @@ -758,18 +900,22 @@ bool ResolvedTypeReferenceMap::addToHash(QCryptographicHash *hash, QQmlEngine *e 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 CompilationUnit::destroy() +{ +#if !defined(V4_BOOTSTRAP) + if (qmlEngine) + QQmlEnginePrivate::deleteInEngineThread(qmlEngine, this); + else +#endif + delete this; +} + + void Unit::generateChecksum() { #ifndef V4_BOOTSTRAP @@ -788,6 +934,60 @@ void Unit::generateChecksum() #endif } +bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const +{ +#ifndef V4_BOOTSTRAP + if (strncmp(magic, CompiledData::magic_str, sizeof(magic))) { + *errorString = QStringLiteral("Magic bytes in the header do not match"); + return false; + } + + if (version != quint32(QV4_DATA_STRUCTURE_VERSION)) { + *errorString = QString::fromUtf8("V4 data structure version mismatch. Found %1 expected %2").arg(version, 0, 16).arg(QV4_DATA_STRUCTURE_VERSION, 0, 16); + return false; + } + + if (qtVersion != quint32(QT_VERSION)) { + *errorString = QString::fromUtf8("Qt version mismatch. Found %1 expected %2").arg(qtVersion, 0, 16).arg(QT_VERSION, 0, 16); + return false; + } + + if (sourceTimeStamp) { + // Files from the resource system do not have any time stamps, so fall back to the application + // executable. + if (!expectedSourceTimeStamp.isValid()) + expectedSourceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified(); + + if (expectedSourceTimeStamp.isValid() && expectedSourceTimeStamp.toMSecsSinceEpoch() != sourceTimeStamp) { + *errorString = QStringLiteral("QML source file has a different time stamp than cached file."); + return false; + } + } + +#if defined(QML_COMPILE_HASH) + if (qstrcmp(CompiledData::qml_compile_hash, libraryVersionHash) != 0) { + *errorString = QStringLiteral("QML library version mismatch. Expected compile hash does not match"); + return false; + } +#else +#error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files" +#endif + + return true; +#else + Q_UNUSED(expectedSourceTimeStamp) + Q_UNUSED(errorString) + return false; +#endif +} + +Location &Location::operator=(const QQmlJS::AST::SourceLocation &astLocation) +{ + line = astLocation.startLine; + column = astLocation.startColumn; + return *this; +} + } } diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index f4ba257cf5..1aaba13241 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -62,19 +62,20 @@ #include <private/qqmlnullablevalue_p.h> #include <private/qv4identifier_p.h> #include <private/qflagpointer_p.h> -#include <private/qjson_p.h> +#include <private/qendian_p.h> +#include <private/qqmljsastfwd_p.h> #ifndef V4_BOOTSTRAP #include <private/qqmltypenamecache_p.h> -#include <private/qqmlpropertycache_p.h> +#include <private/qqmlpropertycachevector_p.h> +#include "private/qintrusivelist_p.h" #endif QT_BEGIN_NAMESPACE // Bump this whenever the compiler data structures change in an incompatible way. -#define QV4_DATA_STRUCTURE_VERSION 0x11 +#define QV4_DATA_STRUCTURE_VERSION 0x1b class QIODevice; -class QQmlPropertyCache; class QQmlPropertyData; class QQmlTypeNameCache; class QQmlScriptData; @@ -86,9 +87,10 @@ struct Document; } namespace QV4 { -namespace IR { -struct Function; -} + +namespace Heap { +struct Module; +}; struct Function; class EvalISelFactory; @@ -96,13 +98,6 @@ 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; @@ -122,85 +117,145 @@ struct TableIterator 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 { union { - QJsonPrivate::qle_bitfield<0, 20> line; - QJsonPrivate::qle_bitfield<20, 12> column; + quint32 _dummy; + quint32_le_bitfield<0, 20> line; + quint32_le_bitfield<20, 12> column; }; - Location() { line.val = 0; column.val = 0; } + Location() : _dummy(0) { } + + Location &operator=(const QQmlJS::AST::SourceLocation &astLocation); inline bool operator<(const Location &other) const { return line < other.line || (line == other.line && column < other.column); } }; +static_assert(sizeof(Location) == 4, "Location structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct RegExp { enum Flags : unsigned int { + RegExp_NoFlags = 0x0, RegExp_Global = 0x01, RegExp_IgnoreCase = 0x02, - RegExp_Multiline = 0x04 + RegExp_Multiline = 0x04, + RegExp_Unicode = 0x08, + RegExp_Sticky = 0x10 }; union { - QJsonPrivate::qle_bitfield<0, 4> flags; - QJsonPrivate::qle_bitfield<4, 28> stringIndex; + quint32 _dummy; + quint32_le_bitfield<0, 5> flags; + quint32_le_bitfield<5, 27> stringIndex; }; - RegExp() { flags.val = 0; stringIndex.val = 0; } + RegExp() : _dummy(0) { } }; +static_assert(sizeof(RegExp) == 4, "RegExp structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Lookup { enum Type : unsigned int { Type_Getter = 0x0, Type_Setter = 0x1, - Type_GlobalGetter = 2, - Type_IndexedGetter = 3, - Type_IndexedSetter = 4 + Type_GlobalGetter = 2 }; union { - QJsonPrivate::qle_bitfield<0, 4> type_and_flags; - QJsonPrivate::qle_bitfield<4, 28> nameIndex; + quint32 _dummy; + quint32_le_bitfield<0, 4> type_and_flags; + quint32_le_bitfield<4, 28> nameIndex; }; - Lookup() { type_and_flags.val = 0; nameIndex.val = 0; } + Lookup() : _dummy(0) { } }; +static_assert(sizeof(Lookup) == 4, "Lookup structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct JSClassMember { union { - QJsonPrivate::qle_bitfield<0, 31> nameOffset; - QJsonPrivate::qle_bitfield<31, 1> isAccessor; + quint32 _dummy; + quint32_le_bitfield<0, 31> nameOffset; + quint32_le_bitfield<31, 1> isAccessor; }; - JSClassMember() { nameOffset = 0; isAccessor = 0; } + JSClassMember() : _dummy(0) { } }; +static_assert(sizeof(JSClassMember) == 4, "JSClassMember structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct JSClass { - LEUInt32 nMembers; + quint32_le nMembers; // JSClassMember[nMembers] static int calculateSize(int nMembers) { return (sizeof(JSClass) + nMembers * sizeof(JSClassMember) + 7) & ~7; } }; - +static_assert(sizeof(JSClass) == 4, "JSClass structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +// This data structure is intended to be binary compatible with QStringData/QStaticStringData on +// 64-bit and 32-bit little-endian architectures, in all directions. So the same structure mapped +// from a file must be castable to a QStringData regardless of the pointer size. With the first +// few fields that's easy, they're always 32-bit. However the offset field of QArrayData is a +// ptrdiff_t and thus variable in size. +// On 64-bit systems compilers enforce an 8-byte alignment and thus place it at offset 16, while +// on 32-bit systems offset 12 is sufficient. Therefore the two values don't overlap and contain +// the same value. struct String { - LEInt32 size; + qint32_le refcount; // -1 + qint32_le size; + quint32_le allocAndCapacityReservedFlag; // 0 + quint32_le offsetOn32Bit; + quint64_le offsetOn64Bit; // uint16 strdata[] static int calculateSize(const QString &str) { - return (sizeof(String) + str.length() * sizeof(quint16) + 7) & ~0x7; + return (sizeof(String) + (str.length() + 1) * sizeof(quint16) + 7) & ~0x7; + } +}; +static_assert(sizeof(String) == 24, "String structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +// Ensure compatibility with QString +static_assert(offsetof(QArrayData, ref) == offsetof(String, refcount), "refcount must be at the same location"); +static_assert(offsetof(QArrayData, size) == offsetof(String, size), "size must be at the same location"); +static_assert(offsetof(String, offsetOn64Bit) == 16, "offset must be at 8-byte aligned location"); +static_assert(offsetof(String, offsetOn32Bit) == 12, "offset must be at 4-byte aligned location"); +#if QT_POINTER_SIZE == 8 +static_assert(offsetof(QArrayData, offset) == offsetof(String, offsetOn64Bit), "offset must be at the same location"); +#else +static_assert(offsetof(QArrayData, offset) == offsetof(String, offsetOn32Bit), "offset must be at the same location"); +#endif + +struct CodeOffsetToLine { + quint32_le codeOffset; + quint32_le line; +}; +static_assert(sizeof(CodeOffsetToLine) == 8, "CodeOffsetToLine structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct Block +{ + quint32_le nLocals; + quint32_le localsOffset; + quint16_le sizeOfLocalTemporalDeadZone; + quint16_le padding; + + const quint32_le *localsTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + localsOffset); } + + static int calculateSize(int nLocals) { + int trailingData = nLocals*sizeof (quint32); + size_t size = align(align(sizeof(Block)) + size_t(trailingData)); + Q_ASSERT(size < INT_MAX); + return int(size); + } + + static size_t align(size_t a) { + return (a + 7) & ~size_t(7); } }; +static_assert(sizeof(Block) == 12, "Block structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); // 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. @@ -208,77 +263,193 @@ struct Function { enum Flags : unsigned int { IsStrict = 0x1, - HasDirectEval = 0x2, - UsesArgumentsObject = 0x4, - IsNamedExpression = 0x8, - HasCatchOrWith = 0x10, - CanUseSimpleCall = 0x20 + IsArrowFunction = 0x2, + IsGenerator = 0x4 }; - // 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; + // Absolute offset into file where the code for this function is located. + quint32_le codeOffset; + quint32_le codeSize; + + quint32_le nameIndex; + quint16_le length; + quint16_le nFormals; + quint32_le formalsOffset; // Can't turn this into a calculated offset because of the mutation in CompilationUnit::createUnitData. + quint32_le localsOffset; + quint16_le nLocals; + quint16_le nLineNumbers; + size_t lineNumberOffset() const { return localsOffset + nLocals * sizeof(quint32); } + quint32_le nestedFunctionIndex; // for functions that only return a single closure, used in signal handlers + quint16_le sizeOfLocalTemporalDeadZone; + quint16_le firstTemporalDeadZoneRegister; + quint16_le sizeOfRegisterTemporalDeadZone; + quint16_le nRegisters; Location location; // Qml Extensions Begin - 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) + // Array of resolved ID objects + size_t dependingIdObjectsOffset() const { return lineNumberOffset() + nLineNumbers * sizeof(CodeOffsetToLine); } + quint16_le nDependingIdObjects; + quint16_le nDependingContextProperties; + // Array of int pairs (property index and notify index) + size_t dependingContextPropertiesOffset() const { return dependingIdObjectsOffset() + nDependingIdObjects * sizeof(quint32); } + quint16_le nDependingScopeProperties; + // Array of int pairs (property index and notify index) + size_t dependingScopePropertiesOffset() const { return dependingContextPropertiesOffset() + nDependingContextProperties * sizeof(quint32); } // Qml Extensions End -// quint32 formalsIndex[nFormals] -// quint32 localsIndex[nLocals] -// quint32 offsetForInnerFunctions[nInnerFunctions] -// Function[nInnerFunctions] + typedef quint16_le TraceInfoCount; + TraceInfoCount nTraceInfos; + static constexpr TraceInfoCount NoTracing() { return TraceInfoCount::max(); } + + quint32_le nLabelInfos; + size_t labelInfosOffset() const { return dependingScopePropertiesOffset() + nDependingScopeProperties; } // Keep all unaligned data at the end quint8 flags; + quint8 padding1; + quint16 padding2; + + // quint32 formalsIndex[nFormals] + // quint32 localsIndex[nLocals] - 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); } + const quint32_le *formalsTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + formalsOffset); } + const quint32_le *localsTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + localsOffset); } + const CodeOffsetToLine *lineNumberTable() const { return reinterpret_cast<const CodeOffsetToLine *>(reinterpret_cast<const char *>(this) + lineNumberOffset()); } + const quint32_le *qmlIdObjectDependencyTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + dependingIdObjectsOffset()); } + const quint32_le *qmlContextPropertiesDependencyTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + dependingContextPropertiesOffset()); } + const quint32_le *qmlScopePropertiesDependencyTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + dependingScopePropertiesOffset()); } // --- QQmlPropertyCacheCreator interface - const LEUInt32 *formalsBegin() const { return formalsTable(); } - const LEUInt32 *formalsEnd() const { return formalsTable() + nFormals; } + const quint32_le *formalsBegin() const { return formalsTable(); } + const quint32_le *formalsEnd() const { return formalsTable() + nFormals; } // --- + const quint32_le *labelInfoTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + labelInfosOffset()); } + + const char *code() const { return reinterpret_cast<const char *>(this) + codeOffset; } + inline bool hasQmlDependencies() const { return nDependingIdObjects > 0 || nDependingContextProperties > 0 || nDependingScopeProperties > 0; } - static int calculateSize(int nFormals, int nLocals, int nInnerfunctions, int nIdObjectDependencies, int nPropertyDependencies) { - return (sizeof(Function) + (nFormals + nLocals + nInnerfunctions + nIdObjectDependencies + 2 * nPropertyDependencies) * sizeof(quint32) + 7) & ~0x7; + static int calculateSize(int nFormals, int nLocals, int nLines, int nInnerfunctions, int nIdObjectDependencies, int nPropertyDependencies, int labelInfoSize, int codeSize) { + int trailingData = (nFormals + nLocals + nInnerfunctions + nIdObjectDependencies + labelInfoSize + + 2 * nPropertyDependencies)*sizeof (quint32) + nLines*sizeof(CodeOffsetToLine); + size_t size = align(align(sizeof(Function)) + size_t(trailingData)) + align(codeSize); + Q_ASSERT(size < INT_MAX); + return int(size); + } + + static size_t align(size_t a) { + return (a + 7) & ~size_t(7); + } +}; +static_assert(sizeof(Function) == 60, "Function structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct Method { + enum Type { + Regular, + Getter, + Setter + }; + + quint32_le name; + quint32_le type; + quint32_le function; +}; +static_assert(sizeof(Method) == 12, "Method structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct Class +{ + quint32_le nameIndex; + quint32_le scopeIndex; + quint32_le constructorFunction; + quint32_le nStaticMethods; + quint32_le nMethods; + quint32_le methodTableOffset; + + const Method *methodTable() const { return reinterpret_cast<const Method *>(reinterpret_cast<const char *>(this) + methodTableOffset); } + + static int calculateSize(int nStaticMethods, int nMethods) { + int trailingData = (nStaticMethods + nMethods) * sizeof(Method); + size_t size = align(sizeof(Class) + trailingData); + Q_ASSERT(size < INT_MAX); + return int(size); + } + + static size_t align(size_t a) { + return (a + 7) & ~size_t(7); } }; +static_assert(sizeof(Class) == 24, "Class structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct TemplateObject +{ + quint32_le size; + + static int calculateSize(int size) { + int trailingData = 2 * size * sizeof(quint32_le); + size_t s = align(sizeof(TemplateObject) + trailingData); + Q_ASSERT(s < INT_MAX); + return int(s); + } + + static size_t align(size_t a) { + return (a + 7) & ~size_t(7); + } + + const quint32_le *stringTable() const { + return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this + 1)); + } + + uint stringIndexAt(uint i) const { + return stringTable()[i]; + } + uint rawStringIndexAt(uint i) const { + return stringTable()[size + i]; + } +}; +static_assert(sizeof(TemplateObject) == 4, "Template object structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct ExportEntry +{ + quint32_le exportName; + quint32_le moduleRequest; + quint32_le importName; + quint32_le localName; + Location location; +}; +static_assert(sizeof(ExportEntry) == 20, "ExportEntry structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct ImportEntry +{ + quint32_le moduleRequest; + quint32_le importName; + quint32_le localName; + Location location; +}; +static_assert(sizeof(ImportEntry) == 16, "ImportEntry structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); // Qml data structures -struct Q_QML_EXPORT TranslationData { - LEUInt32 commentIndex; - LEInt32 number; +struct Q_QML_EXPORT TranslationData +{ + quint32_le stringIndex; + quint32_le commentIndex; + qint32_le number; + quint32_le padding; }; +static_assert(sizeof(TranslationData) == 16, "TranslationData structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Q_QML_PRIVATE_EXPORT Binding { - LEUInt32 propertyNameIndex; + quint32_le propertyNameIndex; enum ValueType : unsigned int { Type_Invalid, Type_Boolean, Type_Number, Type_String, + Type_Null, Type_Translation, Type_TranslationById, Type_Script, @@ -297,20 +468,22 @@ struct Q_QML_PRIVATE_EXPORT Binding IsBindingToAlias = 0x40, IsDeferredBinding = 0x80, IsCustomParserBinding = 0x100, + IsFunctionExpression = 0x200 }; union { - QJsonPrivate::qle_bitfield<0, 16> flags; - QJsonPrivate::qle_bitfield<16, 16> type; + quint32_le_bitfield<0, 16> flags; + quint32_le_bitfield<16, 16> type; }; union { bool b; - quint64 doubleValue; // do not access directly, needs endian protected access - LEUInt32 compiledScriptIndex; // used when Type_Script - LEUInt32 objectIndex; - TranslationData translationData; // used when Type_Translation + quint32_le constantValueIndex; + quint32_le compiledScriptIndex; // used when Type_Script + quint32_le objectIndex; + quint32_le translationDataIndex; // used when Type_Translation + quint32 nullMarker; } value; - LEUInt32 stringIndex; // Set for Type_String, Type_Translation and Type_Script (the latter because of script strings) + quint32_le stringIndex; // Set for Type_String and Type_Script (the latter because of script strings) Location location; Location valueLocation; @@ -362,26 +535,22 @@ struct Q_QML_PRIVATE_EXPORT Binding return false; } + bool isFunctionExpression() const { return (flags & IsFunctionExpression); } + static QString escapedString(const QString &string); - bool evaluatesToString() const { return type == Type_String || type == Type_Translation || type == Type_TranslationById; } + bool isTranslationBinding() const { return type == Type_Translation || type == Type_TranslationById; } + bool evaluatesToString() const { return type == Type_String || isTranslationBinding(); } - QString valueAsString(const Unit *unit) const; - QString valueAsScriptString(const Unit *unit) const; - double valueAsNumber() const +#ifndef V4_BOOTSTRAP + QString valueAsString(const CompilationUnit *unit) const; + QString valueAsScriptString(const CompilationUnit *unit) const; +#endif + double valueAsNumber(const Value *constantTable) const { 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); + return constantTable[value.constantValueIndex].doubleValue(); } bool valueAsBoolean() const @@ -393,18 +562,53 @@ struct Q_QML_PRIVATE_EXPORT Binding }; +static_assert(sizeof(Binding) == 24, "Binding structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct EnumValue +{ + quint32_le nameIndex; + qint32_le value; + Location location; +}; +static_assert(sizeof(EnumValue) == 12, "EnumValue structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct Enum +{ + quint32_le nameIndex; + quint32_le nEnumValues; + Location location; + + const EnumValue *enumValueAt(int idx) const { + return reinterpret_cast<const EnumValue*>(this + 1) + idx; + } + + static int calculateSize(int nEnumValues) { + return (sizeof(Enum) + + nEnumValues * sizeof(EnumValue) + + 7) & ~0x7; + } + + // --- QQmlPropertyCacheCreatorInterface + const EnumValue *enumValuesBegin() const { return enumValueAt(0); } + const EnumValue *enumValuesEnd() const { return enumValueAt(nEnumValues); } + int enumValueCount() const { return nEnumValues; } + // --- +}; +static_assert(sizeof(Enum) == 12, "Enum structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + struct Parameter { - LEUInt32 nameIndex; - LEUInt32 type; - LEUInt32 customTypeNameIndex; + quint32_le nameIndex; + quint32_le type; + quint32_le customTypeNameIndex; Location location; }; +static_assert(sizeof(Parameter) == 16, "Parameter structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Signal { - LEUInt32 nameIndex; - LEUInt32 nParameters; + quint32_le nameIndex; + quint32_le nParameters; Location location; // Parameter parameters[1]; @@ -424,6 +628,7 @@ struct Signal int parameterCount() const { return nParameters; } // --- }; +static_assert(sizeof(Signal) == 12, "Signal structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Property { @@ -436,14 +641,15 @@ struct Property IsReadOnly = 0x1 }; - LEUInt32 nameIndex; + quint32_le nameIndex; union { - QJsonPrivate::qle_bitfield<0, 31> type; - QJsonPrivate::qle_bitfield<31, 1> flags; // readonly + quint32_le_bitfield<0, 31> type; + quint32_le_bitfield<31, 1> flags; // readonly }; - LEUInt32 customTypeNameIndex; // If type >= Custom + quint32_le customTypeNameIndex; // If type >= Custom Location location; }; +static_assert(sizeof(Property) == 16, "Property structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Alias { enum Flags : unsigned int { @@ -452,18 +658,18 @@ struct Alias { AliasPointsToPointerObject = 0x4 }; union { - QJsonPrivate::qle_bitfield<0, 29> nameIndex; - QJsonPrivate::qle_bitfield<29, 3> flags; + quint32_le_bitfield<0, 29> nameIndex; + quint32_le_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; + quint32_le idIndex; // string index + quint32_le_bitfield<0, 31> targetObjectId; // object id index (in QQmlContextData::idValues) + quint32_le_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) + quint32_le propertyNameIndex; // string index + qint32_le encodedMetaPropertyIndex; + quint32_le localAliasIndex; // index in list of aliases local to the object (if targetObjectId == objectId) }; Location location; Location referenceLocation; @@ -473,6 +679,7 @@ struct Alias { return encodedMetaPropertyIndex == -1; } }; +static_assert(sizeof(Alias) == 20, "Alias structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Object { @@ -486,26 +693,28 @@ struct Object // 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. - LEUInt32 inheritedTypeNameIndex; - LEUInt32 idNameIndex; + quint32_le inheritedTypeNameIndex; + quint32_le idNameIndex; union { - QJsonPrivate::qle_bitfield<0, 15> flags; - QJsonPrivate::qle_bitfield<15, 1> defaultPropertyIsAlias; - QJsonPrivate::qle_signedbitfield<16, 16> id; + quint32_le_bitfield<0, 15> flags; + quint32_le_bitfield<15, 1> defaultPropertyIsAlias; + qint32_le_bitfield<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; + qint32_le indexOfDefaultPropertyOrAlias; // -1 means no default property declared in this object + quint16_le nFunctions; + quint16_le nProperties; + quint32_le offsetToFunctions; + quint32_le offsetToProperties; + quint32_le offsetToAliases; + quint16_le nAliases; + quint16_le nEnums; + quint32_le offsetToEnums; // which in turn will be a table with offsets to variable-sized Enum objects + quint32_le offsetToSignals; // which in turn will be a table with offsets to variable-sized Signal objects + quint16_le nSignals; + quint16_le nBindings; + quint32_le offsetToBindings; + quint32_le nNamedObjectsInComponent; + quint32_le offsetToNamedObjectsInComponent; Location location; Location locationOfIdProperty; // Function[] @@ -513,12 +722,13 @@ struct Object // Signal[] // Binding[] - static int calculateSizeExcludingSignals(int nFunctions, int nProperties, int nAliases, int nSignals, int nBindings, int nNamedObjectsInComponent) + static int calculateSizeExcludingSignalsAndEnums(int nFunctions, int nProperties, int nAliases, int nEnums, int nSignals, int nBindings, int nNamedObjectsInComponent) { return ( sizeof(Object) + nFunctions * sizeof(quint32) + nProperties * sizeof(Property) + nAliases * sizeof(Alias) + + nEnums * sizeof(quint32) + nSignals * sizeof(quint32) + nBindings * sizeof(Binding) + nNamedObjectsInComponent * sizeof(int) @@ -526,9 +736,9 @@ struct Object ) & ~0x7; } - const LEUInt32 *functionOffsetTable() const + const quint32_le *functionOffsetTable() const { - return reinterpret_cast<const LEUInt32*>(reinterpret_cast<const char *>(this) + offsetToFunctions); + return reinterpret_cast<const quint32_le*>(reinterpret_cast<const char *>(this) + offsetToFunctions); } const Property *propertyTable() const @@ -546,21 +756,29 @@ struct Object return reinterpret_cast<const Binding*>(reinterpret_cast<const char *>(this) + offsetToBindings); } + const Enum *enumAt(int idx) const + { + const quint32_le *offsetTable = reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToEnums); + const quint32_le offset = offsetTable[idx]; + return reinterpret_cast<const Enum*>(reinterpret_cast<const char*>(this) + offset); + } + const Signal *signalAt(int idx) const { - const LEUInt32 *offsetTable = reinterpret_cast<const LEUInt32*>((reinterpret_cast<const char *>(this)) + offsetToSignals); - const LEUInt32 offset = offsetTable[idx]; + const quint32_le *offsetTable = reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToSignals); + const quint32_le offset = offsetTable[idx]; return reinterpret_cast<const Signal*>(reinterpret_cast<const char*>(this) + offset); } - const LEUInt32 *namedObjectsInComponentTable() const + const quint32_le *namedObjectsInComponentTable() const { - return reinterpret_cast<const LEUInt32*>(reinterpret_cast<const char *>(this) + offsetToNamedObjectsInComponent); + return reinterpret_cast<const quint32_le*>(reinterpret_cast<const char *>(this) + offsetToNamedObjectsInComponent); } // --- QQmlPropertyCacheCreator interface int propertyCount() const { return nProperties; } int aliasCount() const { return nAliases; } + int enumCount() const { return nEnums; } int signalCount() const { return nSignals; } int functionCount() const { return nFunctions; } @@ -573,6 +791,10 @@ struct Object const Alias *aliasesBegin() const { return aliasTable(); } const Alias *aliasesEnd() const { return aliasTable() + nAliases; } + typedef TableIterator<Enum, Object, &Object::enumAt> EnumIterator; + EnumIterator enumsBegin() const { return EnumIterator(this, 0); } + EnumIterator enumsEnd() const { return EnumIterator(this, nEnums); } + typedef TableIterator<Signal, Object, &Object::signalAt> SignalIterator; SignalIterator signalsBegin() const { return SignalIterator(this, 0); } SignalIterator signalsEnd() const { return SignalIterator(this, nSignals); } @@ -580,6 +802,7 @@ struct Object int namedObjectsInComponentCount() const { return nNamedObjectsInComponent; } // --- }; +static_assert(sizeof(Object) == 68, "Object structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Import { @@ -588,78 +811,111 @@ struct Import ImportFile = 0x2, ImportScript = 0x3 }; - LEUInt32 type; + quint32_le type; - LEUInt32 uriIndex; - LEUInt32 qualifierIndex; + quint32_le uriIndex; + quint32_le qualifierIndex; - LEInt32 majorVersion; - LEInt32 minorVersion; + qint32_le majorVersion; + qint32_le minorVersion; Location location; Import() { type = 0; uriIndex = 0; qualifierIndex = 0; majorVersion = 0; minorVersion = 0; } }; +static_assert(sizeof(Import) == 24, "Import structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct QmlUnit +{ + quint32_le nImports; + quint32_le offsetToImports; + quint32_le nObjects; + quint32_le offsetToObjects; + + 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 quint32_le *offsetTable = reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToObjects); + const quint32_le offset = offsetTable[idx]; + return reinterpret_cast<const Object*>(reinterpret_cast<const char*>(this) + offset); + } +}; +static_assert(sizeof(QmlUnit) == 16, "QmlUnit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +enum { QmlCompileHashSpace = 48 }; static const char magic_str[] = "qv4cdata"; +extern const char qml_compile_hash[QmlCompileHashSpace + 1]; struct Unit { // DO NOT CHANGE THESE FIELDS EVER char magic[8]; - LEUInt32 version; - LEUInt32 qtVersion; - LEInt64 sourceTimeStamp; - LEUInt32 unitSize; // Size of the Unit and any depending data. + quint32_le version; + quint32_le qtVersion; + qint64_le sourceTimeStamp; + quint32_le unitSize; // Size of the Unit and any depending data. // END DO NOT CHANGE THESE FIELDS EVER + char libraryVersionHash[QmlCompileHashSpace]; + 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? - ContainsMachineCode = 0x20, // used to determine if we need to mmap with execute permissions - PendingTypeCompilation = 0x40 // the QML data structures present are incomplete and require type compilation + StaticData = 0x2, // Unit data persistent in memory? + IsSingleton = 0x4, + IsSharedLibrary = 0x8, // .pragma shared? + IsESModule = 0x10, + PendingTypeCompilation = 0x20 // the QML data structures present are incomplete and require type compilation }; - 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; + quint32_le flags; + quint32_le stringTableSize; + quint32_le offsetToStringTable; + quint32_le functionTableSize; + quint32_le offsetToFunctionTable; + quint32_le classTableSize; + quint32_le offsetToClassTable; + quint32_le templateObjectTableSize; + quint32_le offsetToTemplateObjectTable; + quint32_le blockTableSize; + quint32_le offsetToBlockTable; + quint32_le lookupTableSize; + quint32_le offsetToLookupTable; + quint32_le regexpTableSize; + quint32_le offsetToRegexpTable; + quint32_le constantTableSize; + quint32_le offsetToConstantTable; + quint32_le jsClassTableSize; + quint32_le offsetToJSClassTable; + quint32_le translationTableSize; + quint32_le offsetToTranslationTable; + quint32_le localExportEntryTableSize; + quint32_le offsetToLocalExportEntryTable; + quint32_le indirectExportEntryTableSize; + quint32_le offsetToIndirectExportEntryTable; + quint32_le starExportEntryTableSize; + quint32_le offsetToStarExportEntryTable; + quint32_le importEntryTableSize; + quint32_le offsetToImportEntryTable; + quint32_le moduleRequestTableSize; + quint32_le offsetToModuleRequestTable; + qint32_le indexOfRootFunction; + quint32_le sourceFileIndex; + quint32_le finalUrlIndex; + + quint32_le offsetToQmlUnit; + + bool verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const; /* QML specific fields */ - 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 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); + const QmlUnit *qmlUnit() const { + return reinterpret_cast<const QmlUnit *>(reinterpret_cast<const char *>(this) + offsetToQmlUnit); } bool isSingleton() const { @@ -667,21 +923,22 @@ struct Unit } /* end QML specific fields*/ - QString stringAt(int idx) const { - const LEUInt32 *offsetTable = reinterpret_cast<const LEUInt32*>((reinterpret_cast<const char *>(this)) + offsetToStringTable); - const LEUInt32 offset = offsetTable[idx]; + QString stringAtInternal(int idx) const { + Q_ASSERT(idx < int(stringTableSize)); + const quint32_le *offsetTable = reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToStringTable); + const quint32_le 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 + if (flags & StaticData) { + const QStringDataPtr holder = { const_cast<QStringData *>(reinterpret_cast<const QStringData*>(str)) }; + return QString(holder); + } const QChar *characters = reinterpret_cast<const QChar *>(str + 1); - // 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); + const quint16_le *characters = reinterpret_cast<const quint16_le *>(str + 1); QString qstr(str->size, Qt::Uninitialized); QChar *ch = qstr.data(); for (int i = 0; i < str->size; ++i) @@ -690,35 +947,65 @@ struct Unit #endif } - const LEUInt32 *functionOffsetTable() const { return reinterpret_cast<const LEUInt32*>((reinterpret_cast<const char *>(this)) + offsetToFunctionTable); } + const quint32_le *functionOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToFunctionTable); } + const quint32_le *classOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToClassTable); } + const quint32_le *templateObjectOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToTemplateObjectTable); } + const quint32_le *blockOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToBlockTable); } const Function *functionAt(int idx) const { - const LEUInt32 *offsetTable = functionOffsetTable(); - const LEUInt32 offset = offsetTable[idx]; + const quint32_le *offsetTable = functionOffsetTable(); + const quint32_le offset = offsetTable[idx]; return reinterpret_cast<const Function*>(reinterpret_cast<const char *>(this) + offset); } + const Class *classAt(int idx) const { + const quint32_le *offsetTable = classOffsetTable(); + const quint32_le offset = offsetTable[idx]; + return reinterpret_cast<const Class *>(reinterpret_cast<const char *>(this) + offset); + } + + const TemplateObject *templateObjectAt(int idx) const { + const quint32_le *offsetTable = templateObjectOffsetTable(); + const quint32_le offset = offsetTable[idx]; + return reinterpret_cast<const TemplateObject *>(reinterpret_cast<const char *>(this) + offset); + } + + const Block *blockAt(int idx) const { + const quint32_le *offsetTable = blockOffsetTable(); + const quint32_le offset = offsetTable[idx]; + return reinterpret_cast<const Block *>(reinterpret_cast<const char *>(this) + offset); + } + const Lookup *lookupTable() const { return reinterpret_cast<const Lookup*>(reinterpret_cast<const char *>(this) + offsetToLookupTable); } const RegExp *regexpAt(int index) const { return reinterpret_cast<const RegExp*>(reinterpret_cast<const char *>(this) + offsetToRegexpTable + index * sizeof(RegExp)); } - const LEUInt64 *constants() const { - return reinterpret_cast<const LEUInt64*>(reinterpret_cast<const char *>(this) + offsetToConstantTable); + const quint64_le *constants() const { + return reinterpret_cast<const quint64_le*>(reinterpret_cast<const char *>(this) + offsetToConstantTable); } const JSClassMember *jsClassAt(int idx, int *nMembers) const { - const LEUInt32 *offsetTable = reinterpret_cast<const LEUInt32 *>(reinterpret_cast<const char *>(this) + offsetToJSClassTable); - const LEUInt32 offset = offsetTable[idx]; + const quint32_le *offsetTable = reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + offsetToJSClassTable); + const quint32_le 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)); } + + const TranslationData *translations() const { + return reinterpret_cast<const TranslationData *>(reinterpret_cast<const char *>(this) + offsetToTranslationTable); + } + + const ImportEntry *importEntryTable() const { return reinterpret_cast<const ImportEntry *>(reinterpret_cast<const char *>(this) + offsetToImportEntryTable); } + const ExportEntry *localExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToLocalExportEntryTable); } + const ExportEntry *indirectExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToIndirectExportEntryTable); } + const ExportEntry *starExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToStarExportEntryTable); } + + const quint32_le *moduleRequestTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToModuleRequestTable); } }; -#if defined(Q_CC_MSVC) || defined(Q_CC_GNU) -#pragma pack(pop) -#endif +static_assert(sizeof(Unit) == 248, "Unit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct TypeReference { @@ -755,9 +1042,7 @@ struct TypeReferenceMap : QHash<int, TypeReference> auto propEnd = obj->propertiesEnd(); for ( ; 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); + TypeReference &r = this->add(prop->customTypeNameIndex, prop->location); r.errorWhenNotFound = true; } } @@ -800,44 +1085,86 @@ typedef QVector<QQmlPropertyData*> BindingPropertyData; struct Q_QML_PRIVATE_EXPORT CompilationUnitBase { - QV4::Heap::String **runtimeStrings = 0; // Array + // pointers either to data->constants() or little-endian memory copy. + QV4::Heap::String **runtimeStrings = nullptr; // Array + const Value* constants = nullptr; + QV4::Value *runtimeRegularExpressions = nullptr; + QV4::Heap::InternalClass **runtimeClasses = nullptr; + const Value** imports = nullptr; }; Q_STATIC_ASSERT(std::is_standard_layout<CompilationUnitBase>::value); Q_STATIC_ASSERT(offsetof(CompilationUnitBase, runtimeStrings) == 0); +Q_STATIC_ASSERT(offsetof(CompilationUnitBase, constants) == sizeof(QV4::Heap::String **)); +Q_STATIC_ASSERT(offsetof(CompilationUnitBase, runtimeRegularExpressions) == offsetof(CompilationUnitBase, constants) + sizeof(const Value *)); +Q_STATIC_ASSERT(offsetof(CompilationUnitBase, runtimeClasses) == offsetof(CompilationUnitBase, runtimeRegularExpressions) + sizeof(const Value *)); +Q_STATIC_ASSERT(offsetof(CompilationUnitBase, imports) == offsetof(CompilationUnitBase, runtimeClasses) + sizeof(const Value *)); -struct Q_QML_PRIVATE_EXPORT CompilationUnit : public CompilationUnitBase, public QQmlRefCount +struct Q_QML_PRIVATE_EXPORT CompilationUnit final : public CompilationUnitBase { + const Unit *data = nullptr; + const QmlUnit *qmlData = nullptr; +public: + CompilationUnit(const Unit *unitData = nullptr, const QString &fileName = QString(), const QString &finalUrlString = QString()); #ifdef V4_BOOTSTRAP - CompilationUnit() - : data(0) - {} - virtual ~CompilationUnit() {} + ~CompilationUnit() {} #else - CompilationUnit(); - virtual ~CompilationUnit(); + ~CompilationUnit(); #endif - const Unit *data; + void addref() + { + Q_ASSERT(refCount.load() > 0); + refCount.ref(); + } + + void release() + { + Q_ASSERT(refCount.load() > 0); + if (!refCount.deref()) + destroy(); + } + int count() const + { + return refCount.load(); + } - // 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); + const Unit *unitData() const { return data; } + void setUnitData(const Unit *unitData, const QmlUnit *qmlUnit = nullptr, + const QString &fileName = QString(), const QString &finalUrlString = QString()); #ifndef V4_BOOTSTRAP - ExecutionEngine *engine; - - QString fileName() const { return data->stringAt(data->sourceFileIndex); } + QIntrusiveListNode nextCompilationUnit; + ExecutionEngine *engine = nullptr; + QQmlEnginePrivate *qmlEngine = nullptr; // only used in QML environment for composite types, not in plain QJSEngine case. + + // url() and fileName() shall be used to load the actual QML/JS code or to show errors or + // warnings about that code. They include any potential URL interceptions and thus represent the + // "physical" location of the code. + // + // finalUrl() and finalUrlString() shall be used to resolve further URLs referred to in the code + // They are _not_ intercepted and thus represent the "logical" name for the code. + + QString fileName() const { return m_fileName; } + QString finalUrlString() const { return m_finalUrlString; } QUrl url() const { if (m_url.isNull) m_url = QUrl(fileName()); return m_url; } + QUrl finalUrl() const + { + if (m_finalUrl.isNull) + m_finalUrl = QUrl(finalUrlString()); + return m_finalUrl; + } - QV4::Lookup *runtimeLookups; - QV4::Value *runtimeRegularExpressions; - QV4::InternalClass **runtimeClasses; + QV4::Lookup *runtimeLookups = nullptr; QVector<QV4::Function *> runtimeFunctions; + QVector<QV4::Heap::InternalClass *> runtimeBlocks; + mutable QVector<QV4::Heap::Object *> templateObjects; mutable QQmlNullableValue<QUrl> m_url; + mutable QQmlNullableValue<QUrl> m_finalUrl; // QML specific fields QQmlPropertyCacheVector propertyCaches; - QQmlPropertyCache *rootPropertyCache() const { return propertyCaches.at(data->indexOfRootObject); } + QQmlRefPointer<QQmlPropertyCache> rootPropertyCache() const { return propertyCaches.at(/*root object*/0); } QQmlRefPointer<QQmlTypeNameCache> typeNameCache; @@ -848,35 +1175,42 @@ struct Q_QML_PRIVATE_EXPORT CompilationUnit : public CompilationUnitBase, public // 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); + QHash<int, IdentifierHash> namedObjectsPerComponentCache; + inline IdentifierHash namedObjectsPerComponent(int componentObjectIndex); - // pointers either to data->constants() or little-endian memory copy. - const Value* constants; - - void finalize(QQmlEnginePrivate *engine); + void finalizeCompositeType(QQmlEnginePrivate *qmlEngine); - 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 + int totalBindingsCount = 0; // Number of bindings used in this type + int totalParserStatusCount = 0; // Number of instantiated types that are QQmlParserStatus subclasses + int totalObjectCount = 0; // Number of objects explicitly instantiated - QVector<QQmlScriptData *> dependentScripts; + QVector<QQmlRefPointer<QQmlScriptData>> dependentScripts; ResolvedTypeReferenceMap resolvedTypes; + ResolvedTypeReference *resolvedType(int id) const { return resolvedTypes.value(id); } bool verifyChecksum(const DependentTypesHasher &dependencyHasher) const; - int metaTypeId; - int listMetaTypeId; - bool isRegisteredWithEngine; + int metaTypeId = -1; + int listMetaTypeId = -1; + bool isRegisteredWithEngine = false; QScopedPointer<CompilationUnitMapper> backingFile; + QStringList dynamicStrings; // --- 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); } + int objectCount() const { return qmlData->nObjects; } + const Object *objectAt(int index) const { return qmlData->objectAt(index); } + int importCount() const { return qmlData->nImports; } + const Import *importAt(int index) const { return qmlData->importAt(index); } + QString stringAt(int index) const + { + if (uint(index) >= data->stringTableSize) + return dynamicStrings.at(index - data->stringTableSize); + return data->stringAtInternal(index); + } + + Heap::Object *templateObjectAt(int index) const; struct FunctionIterator { @@ -894,43 +1228,75 @@ struct Q_QML_PRIVATE_EXPORT CompilationUnit : public CompilationUnitBase, public FunctionIterator objectFunctionsEnd(const Object *object) const { return FunctionIterator(data, object, object->nFunctions); } // --- + bool isESModule() const { return data->flags & Unit::IsESModule; } + bool isSharedLibrary() const { return data->flags & Unit::IsSharedLibrary; } + QStringList moduleRequests() const; + Heap::Module *instantiate(ExecutionEngine *engine); + const Value *resolveExport(QV4::String *exportName); + QStringList exportedNames() const; + void evaluate(); + void evaluateModuleRequests(); + QV4::Function *linkToEngine(QV4::ExecutionEngine *engine); void unlink(); void markObjects(MarkStack *markStack); - void destroy() Q_DECL_OVERRIDE; + bool loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString); - bool loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, EvalISelFactory *iselFactory, QString *errorString); + static QString localCacheFilePath(const QUrl &url); protected: - virtual void linkBackendToEngine(QV4::ExecutionEngine *engine) = 0; - virtual bool memoryMapCode(QString *errorString); + quint32 totalStringCount() const + { return data->stringTableSize; } + +#else // V4_BOOTSTRAP + QString stringAt(int index) const { return data->stringAtInternal(index); } #endif // V4_BOOTSTRAP +private: + void destroy(); + + struct ResolveSetEntry + { + ResolveSetEntry() {} + ResolveSetEntry(CompilationUnit *module, QV4::String *exportName) + : module(module), exportName(exportName) {} + CompilationUnit *module = nullptr; + QV4::String *exportName = nullptr; + }; + + const Value *resolveExportRecursively(QV4::String *exportName, QVector<ResolveSetEntry> *resolveSet); + const ExportEntry *lookupNameInExportTable(const ExportEntry *firstExportEntry, int tableSize, QV4::String *name) const; + void getExportedNamesRecursively(QStringList *names, QVector<const CompilationUnit *> *exportNameSet, bool includeDefaultExport = true) const; + + QString m_fileName; // initialized from data->sourceFileIndex + QString m_finalUrlString; // initialized from data->finalUrlIndex + + QAtomicInt refCount = 1; + + Q_NEVER_INLINE IdentifierHash createNamedObjectsPerComponent(int componentObjectIndex); + + Heap::Module *m_module = nullptr; + public: #if defined(V4_BOOTSTRAP) bool saveToDisk(const QString &outputFileName, QString *errorString); #else bool saveToDisk(const QUrl &unitUrl, QString *errorString); #endif - -protected: - virtual void prepareCodeOffsetsForDiskStorage(CompiledData::Unit *unit); - virtual bool saveCodeToDisk(QIODevice *device, const CompiledData::Unit *unit, QString *errorString); }; #ifndef V4_BOOTSTRAP struct ResolvedTypeReference { ResolvedTypeReference() - : type(0) - , majorVersion(0) + : majorVersion(0) , minorVersion(0) , isFullyDynamicType(false) {} - QQmlType *type; + QQmlType type; QQmlRefPointer<QQmlPropertyCache> typePropertyCache; QQmlRefPointer<QV4::CompiledData::CompilationUnit> compilationUnit; @@ -940,17 +1306,24 @@ struct ResolvedTypeReference // therefore cannot have a property cache installed when instantiated. bool isFullyDynamicType; - QQmlPropertyCache *propertyCache() const; - QQmlPropertyCache *createPropertyCache(QQmlEngine *); + QQmlRefPointer<QQmlPropertyCache> propertyCache() const; + QQmlRefPointer<QQmlPropertyCache> createPropertyCache(QQmlEngine *); bool addToHash(QCryptographicHash *hash, QQmlEngine *engine); void doDynamicTypeCheck(); }; -#endif +IdentifierHash CompilationUnit::namedObjectsPerComponent(int componentObjectIndex) +{ + auto it = namedObjectsPerComponentCache.find(componentObjectIndex); + if (Q_UNLIKELY(it == namedObjectsPerComponentCache.end())) + return createNamedObjectsPerComponent(componentObjectIndex); + return *it; } +#endif // V4_BOOTSTRAP -} +} // CompiledData namespace +} // QV4 namespace Q_DECLARE_TYPEINFO(QV4::CompiledData::JSClassMember, Q_PRIMITIVE_TYPE); diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index f7e63437e1..0833f552e6 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -39,10 +39,12 @@ #include <qv4compiler_p.h> #include <qv4compileddata_p.h> -#include <qv4isel_p.h> +#include <qv4codegen_p.h> #include <private/qv4string_p.h> #include <private/qv4value_p.h> #include <private/qv4alloca_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsast_p.h> #include <wtf/MathExtras.h> #include <QCryptographicHash> @@ -53,6 +55,7 @@ QV4::Compiler::StringTableGenerator::StringTableGenerator() int QV4::Compiler::StringTableGenerator::registerString(const QString &str) { + Q_ASSERT(!frozen); QHash<QString, int>::ConstIterator it = stringToId.constFind(str); if (it != stringToId.cend()) return *it; @@ -73,96 +76,114 @@ void QV4::Compiler::StringTableGenerator::clear() strings.clear(); stringToId.clear(); stringDataSize = 0; + frozen = false; +} + +void QV4::Compiler::StringTableGenerator::initializeFromBackingUnit(const QV4::CompiledData::Unit *unit) +{ + clear(); + for (uint i = 0; i < unit->stringTableSize; ++i) + registerString(unit->stringAtInternal(i)); + backingUnitTableSize = unit->stringTableSize; + stringDataSize = 0; } void QV4::Compiler::StringTableGenerator::serialize(CompiledData::Unit *unit) { char *dataStart = reinterpret_cast<char *>(unit); - 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; + quint32_le *stringTable = reinterpret_cast<quint32_le *>(dataStart + unit->offsetToStringTable); + char *stringData = reinterpret_cast<char *>(stringTable) + WTF::roundUpToMultipleOf(8, unit->stringTableSize * sizeof(uint)); + for (int i = backingUnitTableSize ; i < strings.size(); ++i) { + const int index = i - backingUnitTableSize; + stringTable[index] = stringData - dataStart; const QString &qstr = strings.at(i); QV4::CompiledData::String *s = reinterpret_cast<QV4::CompiledData::String *>(stringData); + Q_ASSERT(reinterpret_cast<uintptr_t>(s) % alignof(QV4::CompiledData::String) == 0); + s->refcount = -1; s->size = qstr.length(); + s->allocAndCapacityReservedFlag = 0; + s->offsetOn32Bit = sizeof(QV4::CompiledData::String); + s->offsetOn64Bit = sizeof(QV4::CompiledData::String); + + ushort *uc = reinterpret_cast<ushort *>(reinterpret_cast<char *>(s) + sizeof(*s)); #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - memcpy(s + 1, qstr.constData(), qstr.length()*sizeof(ushort)); + memcpy(uc, qstr.constData(), s->size * sizeof(ushort)); #else - ushort *uc = reinterpret_cast<ushort *>(s + 1); - for (int i = 0; i < qstr.length(); ++i) + for (int i = 0; i < s->size; ++i) uc[i] = qToLittleEndian<ushort>(qstr.at(i).unicode()); #endif + uc[s->size] = 0; stringData += QV4::CompiledData::String::calculateSize(qstr); } } -QV4::Compiler::JSUnitGenerator::JSUnitGenerator(QV4::IR::Module *module) - : irModule(module) +QV4::Compiler::JSUnitGenerator::JSUnitGenerator(QV4::Compiler::Module *module) + : module(module) { // Make sure the empty string always gets index 0 registerString(QString()); } -uint QV4::Compiler::JSUnitGenerator::registerIndexedGetterLookup() +int QV4::Compiler::JSUnitGenerator::registerGetterLookup(const QString &name) { - CompiledData::Lookup l; - l.type_and_flags = CompiledData::Lookup::Type_IndexedGetter; - l.nameIndex = 0; - lookups << l; - return lookups.size() - 1; + return registerGetterLookup(registerString(name)); } -uint QV4::Compiler::JSUnitGenerator::registerIndexedSetterLookup() +int QV4::Compiler::JSUnitGenerator::registerGetterLookup(int nameIndex) { CompiledData::Lookup l; - l.type_and_flags = CompiledData::Lookup::Type_IndexedSetter; - l.nameIndex = 0; + l.type_and_flags = CompiledData::Lookup::Type_Getter; + l.nameIndex = nameIndex; lookups << l; return lookups.size() - 1; } -uint QV4::Compiler::JSUnitGenerator::registerGetterLookup(const QString &name) +int QV4::Compiler::JSUnitGenerator::registerSetterLookup(const QString &name) { - CompiledData::Lookup l; - l.type_and_flags = CompiledData::Lookup::Type_Getter; - l.nameIndex = registerString(name); - lookups << l; - return lookups.size() - 1; + return registerSetterLookup(registerString(name)); } - -uint QV4::Compiler::JSUnitGenerator::registerSetterLookup(const QString &name) +int QV4::Compiler::JSUnitGenerator::registerSetterLookup(int nameIndex) { CompiledData::Lookup l; l.type_and_flags = CompiledData::Lookup::Type_Setter; - l.nameIndex = registerString(name); + l.nameIndex = nameIndex; lookups << l; return lookups.size() - 1; } -uint QV4::Compiler::JSUnitGenerator::registerGlobalGetterLookup(const QString &name) +int QV4::Compiler::JSUnitGenerator::registerGlobalGetterLookup(const QString &name) +{ + return registerGlobalGetterLookup(registerString(name)); +} + +int QV4::Compiler::JSUnitGenerator::registerGlobalGetterLookup(int nameIndex) { CompiledData::Lookup l; l.type_and_flags = CompiledData::Lookup::Type_GlobalGetter; - l.nameIndex = registerString(name); + l.nameIndex = nameIndex; lookups << l; return lookups.size() - 1; } -int QV4::Compiler::JSUnitGenerator::registerRegExp(QV4::IR::RegExp *regexp) +int QV4::Compiler::JSUnitGenerator::registerRegExp(QQmlJS::AST::RegExpLiteral *regexp) { CompiledData::RegExp re; - re.stringIndex = registerString(*regexp->value); + re.stringIndex = registerString(regexp->pattern.toString()); re.flags = 0; - if (regexp->flags & QV4::IR::RegExp::RegExp_Global) + if (regexp->flags & QQmlJS::Lexer::RegExp_Global) re.flags |= CompiledData::RegExp::RegExp_Global; - if (regexp->flags & QV4::IR::RegExp::RegExp_IgnoreCase) + if (regexp->flags & QQmlJS::Lexer::RegExp_IgnoreCase) re.flags |= CompiledData::RegExp::RegExp_IgnoreCase; - if (regexp->flags & QV4::IR::RegExp::RegExp_Multiline) + if (regexp->flags & QQmlJS::Lexer::RegExp_Multiline) re.flags |= CompiledData::RegExp::RegExp_Multiline; + if (regexp->flags & QQmlJS::Lexer::RegExp_Unicode) + re.flags |= CompiledData::RegExp::RegExp_Unicode; + if (regexp->flags & QQmlJS::Lexer::RegExp_Sticky) + re.flags |= CompiledData::RegExp::RegExp_Sticky; regexps.append(re); return regexps.size() - 1; @@ -177,70 +198,119 @@ int QV4::Compiler::JSUnitGenerator::registerConstant(QV4::ReturnedValue v) return constants.size() - 1; } -int QV4::Compiler::JSUnitGenerator::registerJSClass(int count, IR::ExprList *args) +QV4::ReturnedValue QV4::Compiler::JSUnitGenerator::constant(int idx) +{ + return constants.at(idx); +} + +int QV4::Compiler::JSUnitGenerator::registerJSClass(const QStringList &members) { // ### re-use existing class definitions. - const int size = CompiledData::JSClass::calculateSize(count); + const int size = CompiledData::JSClass::calculateSize(members.size()); jsClassOffsets.append(jsClassData.size()); const int oldSize = jsClassData.size(); jsClassData.resize(jsClassData.size() + size); memset(jsClassData.data() + oldSize, 0, size); CompiledData::JSClass *jsClass = reinterpret_cast<CompiledData::JSClass*>(jsClassData.data() + oldSize); - jsClass->nMembers = count; + jsClass->nMembers = members.size(); 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; - - if (!isData) - it = it->next; + for (const auto &name : members) { + member->nameOffset = registerString(name); + member->isAccessor = false; + ++member; } return jsClassOffsets.size() - 1; } +int QV4::Compiler::JSUnitGenerator::registerTranslation(const QV4::CompiledData::TranslationData &translation) +{ + translations.append(translation); + return translations.size() - 1; +} + QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorOption option) { - registerString(irModule->fileName); - for (QV4::IR::Function *f : qAsConst(irModule->functions)) { - registerString(*f->name); - for (int i = 0; i < f->formals.size(); ++i) - registerString(*f->formals.at(i)); + registerString(module->fileName); + registerString(module->finalUrl); + for (Context *f : qAsConst(module->functions)) { + registerString(f->name); + for (int i = 0; i < f->arguments.size(); ++i) + registerString(f->arguments.at(i)); for (int i = 0; i < f->locals.size(); ++i) - registerString(*f->locals.at(i)); + registerString(f->locals.at(i)); + } + for (Context *c : qAsConst(module->blocks)) { + for (int i = 0; i < c->locals.size(); ++i) + registerString(c->locals.at(i)); + } + { + const auto registerExportEntry = [this](const Compiler::ExportEntry &entry) { + registerString(entry.exportName); + registerString(entry.moduleRequest); + registerString(entry.importName); + registerString(entry.localName); + }; + std::for_each(module->localExportEntries.constBegin(), module->localExportEntries.constEnd(), registerExportEntry); + std::for_each(module->indirectExportEntries.constBegin(), module->indirectExportEntries.constEnd(), registerExportEntry); + std::for_each(module->starExportEntries.constBegin(), module->starExportEntries.constEnd(), registerExportEntry); + } + { + for (const auto &entry: module->importEntries) { + registerString(entry.moduleRequest); + registerString(entry.importName); + registerString(entry.localName); + } + + for (const QString &request: module->moduleRequests) + registerString(request); } - Q_ALLOCA_VAR(CompiledData::LEUInt32, functionOffsets, irModule->functions.size() * sizeof(CompiledData::LEUInt32)); + Q_ALLOCA_VAR(quint32_le, blockClassAndFunctionOffsets, (module->functions.size() + module->classes.size() + module->templateObjects.size() + module->blocks.size()) * sizeof(quint32_le)); uint jsClassDataOffset = 0; char *dataPtr; CompiledData::Unit *unit; { - QV4::CompiledData::Unit tempHeader = generateHeader(option, functionOffsets, &jsClassDataOffset); + QV4::CompiledData::Unit tempHeader = generateHeader(option, blockClassAndFunctionOffsets, &jsClassDataOffset); dataPtr = reinterpret_cast<char *>(malloc(tempHeader.unitSize)); memset(dataPtr, 0, tempHeader.unitSize); memcpy(&unit, &dataPtr, sizeof(CompiledData::Unit*)); memcpy(unit, &tempHeader, sizeof(tempHeader)); } - memcpy(dataPtr + unit->offsetToFunctionTable, functionOffsets, unit->functionTableSize * sizeof(CompiledData::LEUInt32)); + memcpy(dataPtr + unit->offsetToFunctionTable, blockClassAndFunctionOffsets, unit->functionTableSize * sizeof(quint32_le)); + memcpy(dataPtr + unit->offsetToClassTable, blockClassAndFunctionOffsets + unit->functionTableSize, unit->classTableSize * sizeof(quint32_le)); + memcpy(dataPtr + unit->offsetToTemplateObjectTable, blockClassAndFunctionOffsets + unit->functionTableSize + unit->classTableSize, unit->templateObjectTableSize * sizeof(quint32_le)); + memcpy(dataPtr + unit->offsetToBlockTable, blockClassAndFunctionOffsets + unit->functionTableSize + unit->classTableSize + unit->templateObjectTableSize, unit->blockTableSize * sizeof(quint32_le)); - for (int i = 0; i < irModule->functions.size(); ++i) { - QV4::IR::Function *function = irModule->functions.at(i); - if (function == irModule->rootFunction) + for (int i = 0; i < module->functions.size(); ++i) { + Context *function = module->functions.at(i); + if (function == module->rootContext) unit->indexOfRootFunction = i; - writeFunction(dataPtr + functionOffsets[i], function); + writeFunction(dataPtr + blockClassAndFunctionOffsets[i], function); + } + + for (int i = 0; i < module->classes.size(); ++i) { + const Class &c = module->classes.at(i); + + writeClass(dataPtr + blockClassAndFunctionOffsets[i + module->functions.size()], c); + } + + for (int i = 0; i < module->templateObjects.size(); ++i) { + const TemplateObject &t = module->templateObjects.at(i); + + writeTemplateObject(dataPtr + blockClassAndFunctionOffsets[i + module->functions.size() + module->classes.size()], t); + } + + for (int i = 0; i < module->blocks.size(); ++i) { + Context *block = module->blocks.at(i); + + writeBlock(dataPtr + blockClassAndFunctionOffsets[i + module->classes.size() + module->templateObjects.size() + module->functions.size()], block); } CompiledData::Lookup *lookupsToWrite = reinterpret_cast<CompiledData::Lookup*>(dataPtr + unit->offsetToLookupTable); @@ -254,7 +324,7 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO ReturnedValue *constantTable = reinterpret_cast<ReturnedValue *>(dataPtr + unit->offsetToConstantTable); memcpy(constantTable, constants.constData(), constants.size() * sizeof(ReturnedValue)); #else - CompiledData::LEUInt64 *constantTable = reinterpret_cast<CompiledData::LEUInt64 *>(dataPtr + unit->offsetToConstantTable); + quint64_le *constantTable = reinterpret_cast<quint64_le *>(dataPtr + unit->offsetToConstantTable); for (int i = 0; i < constants.count(); ++i) constantTable[i] = constants.at(i); #endif @@ -263,11 +333,50 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO 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); + quint32_le *jsClassOffsetTable = reinterpret_cast<quint32_le *>(dataPtr + unit->offsetToJSClassTable); for (int i = 0; i < jsClassOffsets.count(); ++i) jsClassOffsetTable[i] = jsClassDataOffset + jsClassOffsets.at(i); } + + memcpy(dataPtr + unit->offsetToTranslationTable, translations.constData(), translations.count() * sizeof(CompiledData::TranslationData)); + + { + const auto populateExportEntryTable = [this, dataPtr](const QVector<Compiler::ExportEntry> &table, quint32_le offset) { + CompiledData::ExportEntry *entryToWrite = reinterpret_cast<CompiledData::ExportEntry *>(dataPtr + offset); + for (const Compiler::ExportEntry &entry: table) { + entryToWrite->exportName = getStringId(entry.exportName); + entryToWrite->moduleRequest = getStringId(entry.moduleRequest); + entryToWrite->importName = getStringId(entry.importName); + entryToWrite->localName = getStringId(entry.localName); + entryToWrite->location = entry.location; + entryToWrite++; + } + }; + populateExportEntryTable(module->localExportEntries, unit->offsetToLocalExportEntryTable); + populateExportEntryTable(module->indirectExportEntries, unit->offsetToIndirectExportEntryTable); + populateExportEntryTable(module->starExportEntries, unit->offsetToStarExportEntryTable); + } + + { + CompiledData::ImportEntry *entryToWrite = reinterpret_cast<CompiledData::ImportEntry *>(dataPtr + unit->offsetToImportEntryTable); + for (const Compiler::ImportEntry &entry: module->importEntries) { + entryToWrite->moduleRequest = getStringId(entry.moduleRequest); + entryToWrite->importName = getStringId(entry.importName); + entryToWrite->localName = getStringId(entry.localName); + entryToWrite->location = entry.location; + entryToWrite++; + } + } + + { + quint32_le *moduleRequestEntryToWrite = reinterpret_cast<quint32_le *>(dataPtr + unit->offsetToModuleRequestTable); + for (const QString &moduleRequest: module->moduleRequests) { + *moduleRequestEntryToWrite = getStringId(moduleRequest); + moduleRequestEntryToWrite++; + } + } + // write strings and string table if (option == GenerateWithStringTable) stringTable.serialize(unit); @@ -277,36 +386,42 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO return unit; } -void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::IR::Function *irFunction) const +void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::Compiler::Context *irFunction) const { QV4::CompiledData::Function *function = (QV4::CompiledData::Function *)f; - quint32 currentOffset = sizeof(QV4::CompiledData::Function); - currentOffset = (currentOffset + 7) & ~quint32(0x7); + quint32 currentOffset = static_cast<quint32>(WTF::roundUpToMultipleOf(8, sizeof(*function))); - function->nameIndex = getStringId(*irFunction->name); + function->nameIndex = getStringId(irFunction->name); function->flags = 0; - if (irFunction->hasDirectEval) - function->flags |= CompiledData::Function::HasDirectEval; - if (irFunction->usesArgumentsObject) - function->flags |= CompiledData::Function::UsesArgumentsObject; if (irFunction->isStrict) function->flags |= CompiledData::Function::IsStrict; - if (irFunction->isNamedExpression) - function->flags |= CompiledData::Function::IsNamedExpression; - if (irFunction->hasTry || irFunction->hasWith) - function->flags |= CompiledData::Function::HasCatchOrWith; - if (irFunction->canUseSimpleCall()) - function->flags |= CompiledData::Function::CanUseSimpleCall; - function->nFormals = irFunction->formals.size(); + if (irFunction->isArrowFunction) + function->flags |= CompiledData::Function::IsArrowFunction; + if (irFunction->isGenerator) + function->flags |= CompiledData::Function::IsGenerator; + function->nestedFunctionIndex = + irFunction->returnsClosure ? quint32(module->functions.indexOf(irFunction->nestedContexts.first())) + : std::numeric_limits<uint32_t>::max(); + function->length = irFunction->formals ? irFunction->formals->length() : 0; + function->nFormals = irFunction->arguments.size(); function->formalsOffset = currentOffset; currentOffset += function->nFormals * sizeof(quint32); + function->sizeOfLocalTemporalDeadZone = irFunction->sizeOfLocalTemporalDeadZone; + function->sizeOfRegisterTemporalDeadZone = irFunction->sizeOfRegisterTemporalDeadZone; + function->firstTemporalDeadZoneRegister = irFunction->firstTemporalDeadZoneRegister; + function->nLocals = irFunction->locals.size(); function->localsOffset = currentOffset; currentOffset += function->nLocals * sizeof(quint32); - function->nInnerFunctions = irFunction->nestedFunctions.size(); + function->nLineNumbers = irFunction->lineNumberMapping.size(); + Q_ASSERT(function->lineNumberOffset() == currentOffset); + currentOffset += function->nLineNumbers * sizeof(CompiledData::CodeOffsetToLine); + + function->nTraceInfos = irFunction->nTraceInfos; + function->nRegisters = irFunction->registerCountInFunction; function->nDependingIdObjects = 0; function->nDependingContextProperties = 0; @@ -314,78 +429,211 @@ void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::IR::Function *i if (!irFunction->idObjectDependencies.isEmpty()) { function->nDependingIdObjects = irFunction->idObjectDependencies.count(); - function->dependingIdObjectsOffset = currentOffset; + Q_ASSERT(function->dependingIdObjectsOffset() == currentOffset); currentOffset += function->nDependingIdObjects * sizeof(quint32); } if (!irFunction->contextObjectPropertyDependencies.isEmpty()) { function->nDependingContextProperties = irFunction->contextObjectPropertyDependencies.count(); - function->dependingContextPropertiesOffset = currentOffset; + Q_ASSERT(function->dependingContextPropertiesOffset() == currentOffset); currentOffset += function->nDependingContextProperties * sizeof(quint32) * 2; } if (!irFunction->scopeObjectPropertyDependencies.isEmpty()) { function->nDependingScopeProperties = irFunction->scopeObjectPropertyDependencies.count(); - function->dependingScopePropertiesOffset = currentOffset; + Q_ASSERT(function->dependingScopePropertiesOffset() == currentOffset); currentOffset += function->nDependingScopeProperties * sizeof(quint32) * 2; } + if (!irFunction->labelInfo.empty()) { + function->nLabelInfos = quint32(irFunction->labelInfo.size()); + Q_ASSERT(function->labelInfosOffset() == currentOffset); + currentOffset += function->nLabelInfos * sizeof(quint32); + } + function->location.line = irFunction->line; function->location.column = irFunction->column; - function->codeOffset = 0; - function->codeSize = 0; + function->codeOffset = currentOffset; + function->codeSize = irFunction->code.size(); // write formals - quint32 *formals = (quint32 *)(f + function->formalsOffset); - for (int i = 0; i < irFunction->formals.size(); ++i) - formals[i] = getStringId(*irFunction->formals.at(i)); + quint32_le *formals = (quint32_le *)(f + function->formalsOffset); + for (int i = 0; i < irFunction->arguments.size(); ++i) + formals[i] = getStringId(irFunction->arguments.at(i)); // write locals - quint32 *locals = (quint32 *)(f + function->localsOffset); + quint32_le *locals = (quint32_le *)(f + function->localsOffset); for (int i = 0; i < irFunction->locals.size(); ++i) - locals[i] = getStringId(*irFunction->locals.at(i)); + locals[i] = getStringId(irFunction->locals.at(i)); + + // write line numbers + memcpy(f + function->lineNumberOffset(), irFunction->lineNumberMapping.constData(), irFunction->lineNumberMapping.size()*sizeof(CompiledData::CodeOffsetToLine)); // write QML dependencies - quint32 *writtenDeps = (quint32 *)(f + function->dependingIdObjectsOffset); + quint32_le *writtenDeps = (quint32_le *)(f + function->dependingIdObjectsOffset()); for (int id : irFunction->idObjectDependencies) { Q_ASSERT(id >= 0); *writtenDeps++ = static_cast<quint32>(id); } - writtenDeps = (quint32 *)(f + function->dependingContextPropertiesOffset); + writtenDeps = (quint32_le *)(f + function->dependingContextPropertiesOffset()); for (auto property : irFunction->contextObjectPropertyDependencies) { *writtenDeps++ = property.key(); // property index *writtenDeps++ = property.value(); // notify index } - writtenDeps = (quint32 *)(f + function->dependingScopePropertiesOffset); + writtenDeps = (quint32_le *)(f + function->dependingScopePropertiesOffset()); for (auto property : irFunction->scopeObjectPropertyDependencies) { *writtenDeps++ = property.key(); // property index *writtenDeps++ = property.value(); // notify index } + + quint32_le *labels = (quint32_le *)(f + function->labelInfosOffset()); + for (unsigned u : irFunction->labelInfo) { + *labels++ = u; + } + + // write byte code + memcpy(f + function->codeOffset, irFunction->code.constData(), irFunction->code.size()); +} + +static_assert(int(QV4::Compiler::Class::Method::Regular) == int(QV4::CompiledData::Method::Regular), "Incompatible layout"); +static_assert(int(QV4::Compiler::Class::Method::Getter) == int(QV4::CompiledData::Method::Getter), "Incompatible layout"); +static_assert(int(QV4::Compiler::Class::Method::Setter) == int(QV4::CompiledData::Method::Setter), "Incompatible layout"); + +void QV4::Compiler::JSUnitGenerator::writeClass(char *b, const QV4::Compiler::Class &c) +{ + QV4::CompiledData::Class *cls = reinterpret_cast<QV4::CompiledData::Class *>(b); + + quint32 currentOffset = sizeof(QV4::CompiledData::Class); + + QVector<Class::Method> allMethods = c.staticMethods; + allMethods += c.methods; + + cls->constructorFunction = c.constructorIndex; + cls->nameIndex = c.nameIndex; + cls->nMethods = c.methods.size(); + cls->nStaticMethods = c.staticMethods.size(); + cls->methodTableOffset = currentOffset; + CompiledData::Method *method = reinterpret_cast<CompiledData::Method *>(b + currentOffset); + + // write methods + for (int i = 0; i < allMethods.size(); ++i) { + method->name = allMethods.at(i).nameIndex; + method->type = allMethods.at(i).type; + method->function = allMethods.at(i).functionIndex; + ++method; + } + + static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); + if (showCode) { + qDebug() << "=== Class " << stringForIndex(cls->nameIndex) << "static methods" << cls->nStaticMethods << "methods" << cls->nMethods; + qDebug() << " constructor:" << cls->constructorFunction; + const char *staticString = ": static "; + for (uint i = 0; i < cls->nStaticMethods + cls->nMethods; ++i) { + if (i == cls->nStaticMethods) + staticString = ": "; + const char *type; + switch (cls->methodTable()[i].type) { + case CompiledData::Method::Getter: + type = "get "; break; + case CompiledData::Method::Setter: + type = "set "; break; + default: + type = ""; + + } + qDebug() << " " << i << staticString << type << stringForIndex(cls->methodTable()[i].name) << cls->methodTable()[i].function; + } + qDebug(); + } } -QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Compiler::JSUnitGenerator::GeneratorOption option, QJsonPrivate::q_littleendian<quint32> *functionOffsets, uint *jsClassDataOffset) +void QV4::Compiler::JSUnitGenerator::writeTemplateObject(char *b, const QV4::Compiler::TemplateObject &t) +{ + QV4::CompiledData::TemplateObject *tmpl = reinterpret_cast<QV4::CompiledData::TemplateObject *>(b); + tmpl->size = t.strings.size(); + + quint32 currentOffset = sizeof(QV4::CompiledData::TemplateObject); + + quint32_le *strings = reinterpret_cast<quint32_le *>(b + currentOffset); + + // write methods + for (int i = 0; i < t.strings.size(); ++i) + strings[i] = t.strings.at(i); + strings += t.strings.size(); + + for (int i = 0; i < t.rawStrings.size(); ++i) + strings[i] = t.rawStrings.at(i); + + static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); + if (showCode) { + qDebug() << "=== TemplateObject size" << tmpl->size; + for (uint i = 0; i < tmpl->size; ++i) { + qDebug() << " " << i << stringForIndex(tmpl->stringIndexAt(i)); + qDebug() << " raw: " << stringForIndex(tmpl->rawStringIndexAt(i)); + } + qDebug(); + } +} + +void QV4::Compiler::JSUnitGenerator::writeBlock(char *b, QV4::Compiler::Context *irBlock) const +{ + QV4::CompiledData::Block *block = reinterpret_cast<QV4::CompiledData::Block *>(b); + + quint32 currentOffset = static_cast<quint32>(WTF::roundUpToMultipleOf(8, sizeof(*block))); + + block->sizeOfLocalTemporalDeadZone = irBlock->sizeOfLocalTemporalDeadZone; + block->nLocals = irBlock->locals.size(); + block->localsOffset = currentOffset; + currentOffset += block->nLocals * sizeof(quint32); + + // write locals + quint32_le *locals = (quint32_le *)(b + block->localsOffset); + for (int i = 0; i < irBlock->locals.size(); ++i) + locals[i] = getStringId(irBlock->locals.at(i)); + + static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); + if (showCode) { + qDebug() << "=== Variables for block" << irBlock->blockIndex; + for (int i = 0; i < irBlock->locals.size(); ++i) + qDebug() << " " << i << ":" << locals[i]; + qDebug(); + } +} + +QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Compiler::JSUnitGenerator::GeneratorOption option, quint32_le *blockAndFunctionOffsets, 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.flags |= module->unitFlags; unit.version = QV4_DATA_STRUCTURE_VERSION; unit.qtVersion = QT_VERSION; + qstrcpy(unit.libraryVersionHash, CompiledData::qml_compile_hash); memset(unit.md5Checksum, 0, sizeof(unit.md5Checksum)); - unit.architectureIndex = registerString(irModule->targetABI.isEmpty() ? QSysInfo::buildAbi() : irModule->targetABI); - unit.codeGeneratorIndex = registerString(codeGeneratorName); memset(unit.dependencyMD5Checksum, 0, sizeof(unit.dependencyMD5Checksum)); quint32 nextOffset = sizeof(CompiledData::Unit); - unit.functionTableSize = irModule->functions.size(); + unit.functionTableSize = module->functions.size(); unit.offsetToFunctionTable = nextOffset; nextOffset += unit.functionTableSize * sizeof(uint); + unit.classTableSize = module->classes.size(); + unit.offsetToClassTable = nextOffset; + nextOffset += unit.classTableSize * sizeof(uint); + + unit.templateObjectTableSize = module->templateObjects.size(); + unit.offsetToTemplateObjectTable = nextOffset; + nextOffset += unit.templateObjectTableSize * sizeof(uint); + + unit.blockTableSize = module->blocks.size(); + unit.offsetToBlockTable = nextOffset; + nextOffset += unit.blockTableSize * sizeof(uint); + unit.lookupTableSize = lookups.count(); unit.offsetToLookupTable = nextOffset; nextOffset += unit.lookupTableSize * sizeof(CompiledData::Lookup); @@ -408,17 +656,76 @@ QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Comp *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; + nextOffset = static_cast<quint32>(WTF::roundUpToMultipleOf(8, nextOffset)); + + unit.translationTableSize = translations.count(); + unit.offsetToTranslationTable = nextOffset; + nextOffset += unit.translationTableSize * sizeof(CompiledData::TranslationData); + + nextOffset = static_cast<quint32>(WTF::roundUpToMultipleOf(8, nextOffset)); + + const auto reserveExportTable = [&nextOffset](int count, quint32_le *tableSizePtr, quint32_le *offsetPtr) { + *tableSizePtr = count; + *offsetPtr = nextOffset; + nextOffset += count * sizeof(CompiledData::ExportEntry); + nextOffset = static_cast<quint32>(WTF::roundUpToMultipleOf(8, nextOffset)); + }; + + reserveExportTable(module->localExportEntries.count(), &unit.localExportEntryTableSize, &unit.offsetToLocalExportEntryTable); + reserveExportTable(module->indirectExportEntries.count(), &unit.indirectExportEntryTableSize, &unit.offsetToIndirectExportEntryTable); + reserveExportTable(module->starExportEntries.count(), &unit.starExportEntryTableSize, &unit.offsetToStarExportEntryTable); + + unit.importEntryTableSize = module->importEntries.count(); + unit.offsetToImportEntryTable = nextOffset; + nextOffset += unit.importEntryTableSize * sizeof(CompiledData::ImportEntry); + nextOffset = static_cast<quint32>(WTF::roundUpToMultipleOf(8, nextOffset)); + + unit.moduleRequestTableSize = module->moduleRequests.count(); + unit.offsetToModuleRequestTable = nextOffset; + nextOffset += unit.moduleRequestTableSize * sizeof(uint); + nextOffset = static_cast<quint32>(WTF::roundUpToMultipleOf(8, nextOffset)); + + quint32 functionSize = 0; + for (int i = 0; i < module->functions.size(); ++i) { + Context *f = module->functions.at(i); + blockAndFunctionOffsets[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); + quint32 size = QV4::CompiledData::Function::calculateSize(f->arguments.size(), f->locals.size(), f->lineNumberMapping.size(), f->nestedContexts.size(), + qmlIdDepsCount, qmlPropertyDepsCount, int(f->labelInfo.size()), f->code.size()); + functionSize += size - f->code.size(); + nextOffset += size; + } + + blockAndFunctionOffsets += module->functions.size(); + + for (int i = 0; i < module->classes.size(); ++i) { + const Class &c = module->classes.at(i); + blockAndFunctionOffsets[i] = nextOffset; + + nextOffset += QV4::CompiledData::Class::calculateSize(c.staticMethods.size(), c.methods.size()); + } + blockAndFunctionOffsets += module->classes.size(); + + for (int i = 0; i < module->templateObjects.size(); ++i) { + const TemplateObject &t = module->templateObjects.at(i); + blockAndFunctionOffsets[i] = nextOffset; + + nextOffset += QV4::CompiledData::TemplateObject::calculateSize(t.strings.size()); + } + blockAndFunctionOffsets += module->templateObjects.size(); + + for (int i = 0; i < module->blocks.size(); ++i) { + Context *c = module->blocks.at(i); + blockAndFunctionOffsets[i] = nextOffset; + + nextOffset += QV4::CompiledData::Block::calculateSize(c->locals.size()); } if (option == GenerateWithStringTable) { unit.stringTableSize = stringTable.stringCount(); + nextOffset = static_cast<quint32>(WTF::roundUpToMultipleOf(8, nextOffset)); unit.offsetToStringTable = nextOffset; nextOffset += stringTable.sizeOfTableAndData(); } else { @@ -426,15 +733,19 @@ QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Comp unit.offsetToStringTable = 0; } unit.indexOfRootFunction = -1; - unit.sourceFileIndex = getStringId(irModule->fileName); - unit.sourceTimeStamp = irModule->sourceTimeStamp.isValid() ? irModule->sourceTimeStamp.toMSecsSinceEpoch() : 0; - unit.nImports = 0; - unit.offsetToImports = 0; - unit.nObjects = 0; - unit.offsetToObjects = 0; - unit.indexOfRootObject = 0; + unit.sourceFileIndex = getStringId(module->fileName); + unit.finalUrlIndex = getStringId(module->finalUrl); + unit.sourceTimeStamp = module->sourceTimeStamp.isValid() ? module->sourceTimeStamp.toMSecsSinceEpoch() : 0; + unit.offsetToQmlUnit = 0; unit.unitSize = nextOffset; + static const bool showStats = qEnvironmentVariableIsSet("QML_SHOW_UNIT_STATS"); + if (showStats) { + qDebug() << "Generated JS unit that is" << unit.unitSize << "bytes contains:"; + qDebug() << " " << functionSize << "bytes for non-code function data for" << unit.functionTableSize << "functions"; + qDebug() << " " << translations.count() * sizeof(CompiledData::TranslationData) << "bytes for" << translations.count() << "translations"; + } + return unit; } diff --git a/src/qml/compiler/qv4compiler_p.h b/src/qml/compiler/qv4compiler_p.h index 49b8664513..2f5889ab53 100644 --- a/src/qml/compiler/qv4compiler_p.h +++ b/src/qml/compiler/qv4compiler_p.h @@ -51,8 +51,11 @@ // #include <QtCore/qstring.h> -#include "qv4jsir_p.h" -#include <private/qjson_p.h> +#include <QtCore/qhash.h> +#include <QtCore/qstringlist.h> +#include <private/qv4global_p.h> +#include <private/qqmljsastfwd_p.h> +#include <private/qv4compileddata_p.h> QT_BEGIN_NAMESPACE @@ -69,44 +72,63 @@ struct JSClassMember; namespace Compiler { +struct Class; +struct TemplateObject; + struct Q_QML_PRIVATE_EXPORT StringTableGenerator { StringTableGenerator(); int registerString(const QString &str); int getStringId(const QString &string) const; QString stringForIndex(int index) const { return strings.at(index); } - uint stringCount() const { return strings.size(); } + uint stringCount() const { return strings.size() - backingUnitTableSize; } + + uint sizeOfTableAndData() const { return stringDataSize + ((stringCount() * sizeof(uint) + 7) & ~7); } - uint sizeOfTableAndData() const { return stringDataSize + strings.count() * sizeof(uint); } + void freeze() { frozen = true; } void clear(); + void initializeFromBackingUnit(const CompiledData::Unit *unit); + void serialize(CompiledData::Unit *unit); + QStringList allStrings() const { return strings.mid(backingUnitTableSize); } private: QHash<QString, int> stringToId; QStringList strings; uint stringDataSize; + uint backingUnitTableSize = 0; + bool frozen = false; }; struct Q_QML_PRIVATE_EXPORT JSUnitGenerator { - JSUnitGenerator(IR::Module *module); + struct MemberInfo { + QString name; + bool isAccessor; + }; + + JSUnitGenerator(Module *module); int registerString(const QString &str) { return stringTable.registerString(str); } int getStringId(const QString &string) const { return stringTable.getStringId(string); } QString stringForIndex(int index) const { return stringTable.stringForIndex(index); } - uint registerGetterLookup(const QString &name); - uint registerSetterLookup(const QString &name); - uint registerGlobalGetterLookup(const QString &name); - uint registerIndexedGetterLookup(); - uint registerIndexedSetterLookup(); + int registerGetterLookup(const QString &name); + int registerGetterLookup(int nameIndex); + int registerSetterLookup(const QString &name); + int registerSetterLookup(int nameIndex); + int registerGlobalGetterLookup(const QString &name); + int registerGlobalGetterLookup(int nameIndex); - int registerRegExp(IR::RegExp *regexp); + int registerRegExp(QQmlJS::AST::RegExpLiteral *regexp); int registerConstant(ReturnedValue v); + ReturnedValue constant(int idx); + + int registerJSClass(const QStringList &members); - int registerJSClass(int count, IR::ExprList *args); + int registerTranslation(const CompiledData::TranslationData &translation); enum GeneratorOption { GenerateWithStringTable, @@ -114,21 +136,24 @@ struct Q_QML_PRIVATE_EXPORT JSUnitGenerator { }; QV4::CompiledData::Unit *generateUnit(GeneratorOption option = GenerateWithStringTable); - // Returns bytes written - void writeFunction(char *f, IR::Function *irFunction) const; + void writeFunction(char *f, Context *irFunction) const; + void writeClass(char *f, const Class &c); + void writeTemplateObject(char *f, const TemplateObject &o); + void writeBlock(char *f, Context *irBlock) const; StringTableGenerator stringTable; QString codeGeneratorName; private: - CompiledData::Unit generateHeader(GeneratorOption option, QJsonPrivate::q_littleendian<quint32> *functionOffsets, uint *jsClassDataOffset); + CompiledData::Unit generateHeader(GeneratorOption option, quint32_le *functionOffsets, uint *jsClassDataOffset); - IR::Module *irModule; + Module *module; QList<CompiledData::Lookup> lookups; QVector<CompiledData::RegExp> regexps; QVector<ReturnedValue> constants; QByteArray jsClassData; QVector<int> jsClassOffsets; + QVector<CompiledData::TranslationData> translations; }; } diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp new file mode 100644 index 0000000000..931759fa72 --- /dev/null +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "qv4compilercontext_p.h" +#include "qv4compilercontrolflow_p.h" +#include "qv4bytecodegenerator_p.h" + +QT_USE_NAMESPACE +using namespace QV4; +using namespace QV4::Compiler; +using namespace QQmlJS::AST; + +QT_BEGIN_NAMESPACE + +Context *Module::newContext(Node *node, Context *parent, ContextType contextType) +{ + Q_ASSERT(!contextMap.contains(node)); + + Context *c = new Context(parent, contextType); + if (node) { + SourceLocation loc = node->firstSourceLocation(); + c->line = loc.startLine; + c->column = loc.startColumn; + } + + contextMap.insert(node, c); + + if (!parent) + rootContext = c; + else { + parent->nestedContexts.append(c); + c->isStrict = parent->isStrict; + } + + return c; +} + +bool Context::Member::requiresTDZCheck(const SourceLocation &accessLocation, bool accessAcrossContextBoundaries) const +{ + if (!isLexicallyScoped()) + return false; + + if (accessAcrossContextBoundaries) + return true; + + if (!accessLocation.isValid() || !endOfInitializerLocation.isValid()) + return true; + + return accessLocation.begin() < endOfInitializerLocation.end(); +} + +bool Context::addLocalVar(const QString &name, Context::MemberType type, VariableScope scope, FunctionExpression *function, + const QQmlJS::AST::SourceLocation &endOfInitializer) +{ + // ### can this happen? + if (name.isEmpty()) + return true; + + if (type != FunctionDefinition) { + if (formals && formals->containsName(name)) + return (scope == VariableScope::Var); + } + if (!isCatchBlock || name != caughtVariable) { + MemberMap::iterator it = members.find(name); + if (it != members.end()) { + if (scope != VariableScope::Var || (*it).scope != VariableScope::Var) + return false; + if ((*it).type <= type) { + (*it).type = type; + (*it).function = function; + } + return true; + } + } + + // hoist var declarations to the function level + if (contextType == ContextType::Block && (scope == VariableScope::Var && type != MemberType::FunctionDefinition)) + return parent->addLocalVar(name, type, scope, function, endOfInitializer); + + Member m; + m.type = type; + m.function = function; + m.scope = scope; + m.endOfInitializerLocation = endOfInitializer; + members.insert(name, m); + return true; +} + +Context::ResolvedName Context::resolveName(const QString &name, const QQmlJS::AST::SourceLocation &accessLocation) +{ + int scope = 0; + Context *c = this; + + ResolvedName result; + + while (c) { + if (c->isWithBlock) + return result; + + Context::Member m = c->findMember(name); + if (!c->parent && m.index < 0) + break; + + if (m.type != Context::UndefinedMember) { + result.type = m.canEscape ? ResolvedName::Local : ResolvedName::Stack; + result.scope = scope; + result.index = m.index; + result.isConst = (m.scope == VariableScope::Const); + result.requiresTDZCheck = m.requiresTDZCheck(accessLocation, c != this); + if (c->isStrict && (name == QLatin1String("arguments") || name == QLatin1String("eval"))) + result.isArgOrEval = true; + return result; + } + const int argIdx = c->findArgument(name); + if (argIdx != -1) { + if (c->argumentsCanEscape) { + result.index = argIdx + c->locals.size(); + result.scope = scope; + result.type = ResolvedName::Local; + result.isConst = false; + return result; + } else { + result.index = argIdx + sizeof(CallData)/sizeof(Value) - 1; + result.scope = 0; + result.type = ResolvedName::Stack; + result.isConst = false; + return result; + } + } + if (c->hasDirectEval) { + Q_ASSERT(!c->isStrict && c->contextType != ContextType::Block); + return result; + } + + if (c->requiresExecutionContext) + ++scope; + c = c->parent; + } + + if (c && c->contextType == ContextType::ESModule) { + for (int i = 0; i < c->importEntries.count(); ++i) { + if (c->importEntries.at(i).localName == name) { + result.index = i; + result.type = ResolvedName::Import; + result.isConst = true; + // We don't know at compile time whether the imported value is let/const or not. + result.requiresTDZCheck = true; + return result; + } + } + } + + // ### can we relax the restrictions here? + if (c->contextType == ContextType::Eval || c->contextType == ContextType::Binding) + return result; + + result.type = ResolvedName::Global; + return result; +} + +void Context::emitBlockHeader(Codegen *codegen) +{ + using Instruction = Moth::Instruction; + Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator(); + + setupFunctionIndices(bytecodeGenerator); + + if (requiresExecutionContext) { + if (blockIndex < 0) { + codegen->module()->blocks.append(this); + blockIndex = codegen->module()->blocks.count() - 1; + } + + if (contextType == ContextType::Global) { + Instruction::PushScriptContext scriptContext; + scriptContext.index = blockIndex; + bytecodeGenerator->addInstruction(scriptContext); + } else if (contextType == ContextType::Block || (contextType == ContextType::Eval && !isStrict)) { + if (isCatchBlock) { + Instruction::PushCatchContext catchContext; + catchContext.index = blockIndex; + catchContext.name = codegen->registerString(caughtVariable); + bytecodeGenerator->addInstruction(catchContext); + } else { + Instruction::PushBlockContext blockContext; + blockContext.index = blockIndex; + bytecodeGenerator->addInstruction(blockContext); + } + } else if (contextType != ContextType::ESModule && contextType != ContextType::ScriptImportedByQML) { + Instruction::CreateCallContext createContext; + bytecodeGenerator->addInstruction(createContext); + } + } + + if (contextType == ContextType::Block && sizeOfRegisterTemporalDeadZone > 0) { + Instruction::InitializeBlockDeadTemporalZone tdzInit; + tdzInit.firstReg = registerOffset + nRegisters - sizeOfRegisterTemporalDeadZone; + tdzInit.count = sizeOfRegisterTemporalDeadZone; + bytecodeGenerator->addInstruction(tdzInit); + } + + if (usesThis) { + Q_ASSERT(!isStrict); + // make sure we convert this to an object + Instruction::ConvertThisToObject convert; + bytecodeGenerator->addInstruction(convert); + } + if (innerFunctionAccessesThis) { + Instruction::LoadReg load; + load.reg = CallData::This; + bytecodeGenerator->addInstruction(load); + Codegen::Reference r = codegen->referenceForName(QStringLiteral("this"), true); + r.storeConsumeAccumulator(); + } + if (innerFunctionAccessesNewTarget) { + Instruction::LoadReg load; + load.reg = CallData::NewTarget; + bytecodeGenerator->addInstruction(load); + Codegen::Reference r = codegen->referenceForName(QStringLiteral("new.target"), true); + r.storeConsumeAccumulator(); + } + + if (contextType == ContextType::Global || contextType == ContextType::ScriptImportedByQML || (contextType == ContextType::Eval && !isStrict)) { + // variables in global code are properties of the global context object, not locals as with other functions. + for (Context::MemberMap::const_iterator it = members.constBegin(), cend = members.constEnd(); it != cend; ++it) { + if (it->isLexicallyScoped()) + continue; + const QString &local = it.key(); + + Instruction::DeclareVar declareVar; + declareVar.isDeletable = (contextType == ContextType::Eval); + declareVar.varName = codegen->registerString(local); + bytecodeGenerator->addInstruction(declareVar); + } + } + + if (contextType == ContextType::Function || contextType == ContextType::Binding || contextType == ContextType::ESModule) { + for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) { + if (it->canEscape && it->type == Context::ThisFunctionName) { + // move the function from the stack to the call context + Instruction::LoadReg load; + load.reg = CallData::Function; + bytecodeGenerator->addInstruction(load); + Instruction::StoreLocal store; + store.index = it->index; + bytecodeGenerator->addInstruction(store); + } + } + } + + if (usesArgumentsObject == Context::ArgumentsObjectUsed) { + Q_ASSERT(contextType != ContextType::Block); + if (isStrict || (formals && !formals->isSimpleParameterList())) { + Instruction::CreateUnmappedArgumentsObject setup; + bytecodeGenerator->addInstruction(setup); + } else { + Instruction::CreateMappedArgumentsObject setup; + bytecodeGenerator->addInstruction(setup); + } + codegen->referenceForName(QStringLiteral("arguments"), false).storeConsumeAccumulator(); + } + + for (const Context::Member &member : qAsConst(members)) { + if (member.function) { + const int function = codegen->defineFunction(member.function->name.toString(), member.function, member.function->formals, member.function->body); + codegen->loadClosure(function); + Codegen::Reference r = codegen->referenceForName(member.function->name.toString(), true); + r.storeConsumeAccumulator(); + } + } +} + +void Context::emitBlockFooter(Codegen *codegen) +{ + using Instruction = Moth::Instruction; + Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator(); + + if (!requiresExecutionContext) + return; + +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty structs. + if (contextType == ContextType::Global) + bytecodeGenerator->addInstruction(Instruction::PopScriptContext()); + else if (contextType != ContextType::ESModule && contextType != ContextType::ScriptImportedByQML) + bytecodeGenerator->addInstruction(Instruction::PopContext()); +QT_WARNING_POP +} + +void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator) +{ + if (registerOffset != -1) { + // already computed, check for consistency + Q_ASSERT(registerOffset == bytecodeGenerator->currentRegister()); + bytecodeGenerator->newRegisterArray(nRegisters); + return; + } + Q_ASSERT(locals.size() == 0); + Q_ASSERT(nRegisters == 0); + registerOffset = bytecodeGenerator->currentRegister(); + + QVector<Context::MemberMap::Iterator> localsInTDZ; + const auto registerLocal = [this, &localsInTDZ](Context::MemberMap::iterator member) { + if (member->isLexicallyScoped()) { + localsInTDZ << member; + } else { + member->index = locals.size(); + locals.append(member.key()); + } + }; + + QVector<Context::MemberMap::Iterator> registersInTDZ; + const auto allocateRegister = [bytecodeGenerator, ®istersInTDZ](Context::MemberMap::iterator member) { + if (member->isLexicallyScoped()) + registersInTDZ << member; + else + member->index = bytecodeGenerator->newRegister(); + }; + + switch (contextType) { + case ContextType::ESModule: + case ContextType::Block: + case ContextType::Function: + case ContextType::Binding: { + for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) { + if (it->canEscape) { + registerLocal(it); + } else { + if (it->type == Context::ThisFunctionName) + it->index = CallData::Function; + else + allocateRegister(it); + } + } + break; + } + case ContextType::Global: + case ContextType::ScriptImportedByQML: + case ContextType::Eval: + for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) { + if (!it->isLexicallyScoped() && (contextType == ContextType::Global || contextType == ContextType::ScriptImportedByQML || !isStrict)) + continue; + if (it->canEscape) + registerLocal(it); + else + allocateRegister(it); + } + break; + } + + sizeOfLocalTemporalDeadZone = localsInTDZ.count(); + for (auto &member: qAsConst(localsInTDZ)) { + member->index = locals.size(); + locals.append(member.key()); + } + + if (contextType == ContextType::ESModule && !localNameForDefaultExport.isEmpty()) { + if (!members.contains(localNameForDefaultExport)) { + // allocate a local slot for the default export, to be used in + // CodeGen::visit(ExportDeclaration*). + locals.append(localNameForDefaultExport); + ++sizeOfLocalTemporalDeadZone; + } + } + + sizeOfRegisterTemporalDeadZone = registersInTDZ.count(); + firstTemporalDeadZoneRegister = bytecodeGenerator->currentRegister(); + for (auto &member: qAsConst(registersInTDZ)) + member->index = bytecodeGenerator->newRegister(); + + nRegisters = bytecodeGenerator->currentRegister() - registerOffset; +} + +bool Context::canUseTracingJit() const +{ +#if QT_CONFIG(qml_tracing) + static bool forceTracing = !qEnvironmentVariableIsEmpty("QV4_FORCE_TRACING"); + if (forceTracing) //### we can probably remove this when tracing is turned on by default + return true; // to be used by unittests + + static bool disableTracing = !qEnvironmentVariableIsEmpty("QV4_DISABLE_TRACING"); + if (disableTracing) + return false; + + static QStringList onlyTrace = + qEnvironmentVariable("QV4_ONLY_TRACE").split(QLatin1Char(','), QString::SkipEmptyParts); + if (!onlyTrace.isEmpty()) + return onlyTrace.contains(name); + + return true; +#else + return false; +#endif +} + +QT_END_NAMESPACE diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h new file mode 100644 index 0000000000..5b91b93346 --- /dev/null +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -0,0 +1,380 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QV4COMPILERCONTEXT_P_H +#define QV4COMPILERCONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qv4global_p.h" +#include <private/qqmljsast_p.h> +#include <private/qv4compileddata_p.h> +#include <QtCore/QStringList> +#include <QtCore/QDateTime> +#include <QtCore/QStack> +#include <QtCore/QHash> + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace Compiler { + +struct ControlFlow; + +enum class ContextType { + Global, + Function, + Eval, + Binding, // This is almost the same as Eval, except: + // * function declarations are moved to the return address when encountered + // * return statements are allowed everywhere (like in FunctionCode) + // * variable declarations are treated as true locals (like in FunctionCode) + Block, + ESModule, + ScriptImportedByQML, +}; + +struct Context; + +struct Class { + struct Method { + enum Type { + Regular, + Getter, + Setter + }; + uint nameIndex; + Type type; + uint functionIndex; + }; + + uint nameIndex; + uint constructorIndex = UINT_MAX; + QVector<Method> staticMethods; + QVector<Method> methods; +}; + +struct TemplateObject { + QVector<uint> strings; + QVector<uint> rawStrings; + bool operator==(const TemplateObject &other) { + return strings == other.strings && rawStrings == other.rawStrings; + } +}; + +struct ExportEntry +{ + QString exportName; + QString moduleRequest; + QString importName; + QString localName; + CompiledData::Location location; + + static bool lessThan(const ExportEntry &lhs, const ExportEntry &rhs) + { return lhs.exportName < rhs.exportName; } +}; + +struct ImportEntry +{ + QString moduleRequest; + QString importName; + QString localName; + CompiledData::Location location; +}; + +struct Module { + Module(bool debugMode) + : debugMode(debugMode) + {} + ~Module() { + qDeleteAll(contextMap); + } + + Context *newContext(QQmlJS::AST::Node *node, Context *parent, ContextType compilationMode); + + QHash<QQmlJS::AST::Node *, Context *> contextMap; + QList<Context *> functions; + QList<Context *> blocks; + QVector<Class> classes; + QVector<TemplateObject> templateObjects; + Context *rootContext; + QString fileName; + QString finalUrl; + QDateTime sourceTimeStamp; + uint unitFlags = 0; // flags merged into CompiledData::Unit::flags + bool debugMode = false; + QVector<ExportEntry> localExportEntries; + QVector<ExportEntry> indirectExportEntries; + QVector<ExportEntry> starExportEntries; + QVector<ImportEntry> importEntries; + QStringList moduleRequests; +}; + + +struct Context { + Context *parent; + QString name; + int line = 0; + int column = 0; + int registerCountInFunction = 0; + uint nTraceInfos = 0; + int functionIndex = -1; + int blockIndex = -1; + + enum MemberType { + UndefinedMember, + ThisFunctionName, + VariableDefinition, + VariableDeclaration, + FunctionDefinition + }; + + struct Member { + MemberType type = UndefinedMember; + int index = -1; + QQmlJS::AST::VariableScope scope = QQmlJS::AST::VariableScope::Var; + mutable bool canEscape = false; + QQmlJS::AST::FunctionExpression *function = nullptr; + QQmlJS::AST::SourceLocation endOfInitializerLocation; + + bool isLexicallyScoped() const { return this->scope != QQmlJS::AST::VariableScope::Var; } + bool requiresTDZCheck(const QQmlJS::AST::SourceLocation &accessLocation, bool accessAcrossContextBoundaries) const; + }; + typedef QMap<QString, Member> MemberMap; + + MemberMap members; + QSet<QString> usedVariables; + QQmlJS::AST::FormalParameterList *formals = nullptr; + QStringList arguments; + QStringList locals; + QStringList moduleRequests; + QVector<ImportEntry> importEntries; + QVector<ExportEntry> exportEntries; + QString localNameForDefaultExport; + QVector<Context *> nestedContexts; + + ControlFlow *controlFlow = nullptr; + QByteArray code; + QVector<CompiledData::CodeOffsetToLine> lineNumberMapping; + std::vector<unsigned> labelInfo; + + int nRegisters = 0; + int registerOffset = -1; + int sizeOfLocalTemporalDeadZone = 0; + int firstTemporalDeadZoneRegister = 0; + int sizeOfRegisterTemporalDeadZone = 0; + bool hasDirectEval = false; + bool allVarsEscape = false; + bool hasNestedFunctions = false; + bool isStrict = false; + bool isArrowFunction = false; + bool isGenerator = false; + bool usesThis = false; + bool innerFunctionAccessesThis = false; + bool innerFunctionAccessesNewTarget = false; + bool hasTry = false; + bool returnsClosure = false; + mutable bool argumentsCanEscape = false; + bool requiresExecutionContext = false; + bool isWithBlock = false; + bool isCatchBlock = false; + QString caughtVariable; + QQmlJS::AST::SourceLocation lastBlockInitializerLocation; + + enum UsesArgumentsObject { + ArgumentsObjectUnknown, + ArgumentsObjectNotUsed, + ArgumentsObjectUsed + }; + + UsesArgumentsObject usesArgumentsObject = ArgumentsObjectUnknown; + + ContextType contextType; + + 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 + { + quint32 _key = 0; + quint32 _value = 0; + + KeyValuePair() {} + KeyValuePair(quint32 key, quint32 value): _key(key), _value(value) {} + + quint32 key() const { return _key; } + quint32 value() const { return _value; } + }; + + class PropertyDependencyMap: public QVarLengthArray<KeyValuePair, 8> + { + public: + void insert(quint32 key, quint32 value) + { + for (auto it = begin(), eit = end(); it != eit; ++it) { + if (it->_key == key) { + it->_value = value; + return; + } + } + append(KeyValuePair(key, value)); + } + }; + + // Qml extension: + SmallSet<int> idObjectDependencies; + PropertyDependencyMap contextObjectPropertyDependencies; + PropertyDependencyMap scopeObjectPropertyDependencies; + + Context(Context *parent, ContextType type) + : parent(parent) + , contextType(type) + { + if (parent && parent->isStrict) + isStrict = true; + } + + int findArgument(const QString &name) const + { + // search backwards to handle duplicate argument names correctly + for (int i = arguments.size() - 1; i >= 0; --i) { + if (arguments.at(i) == name) + return i; + } + return -1; + } + + Member findMember(const QString &name) const + { + MemberMap::const_iterator it = members.find(name); + if (it == members.end()) + return Member(); + Q_ASSERT(it->index != -1 || !parent); + return (*it); + } + + bool memberInfo(const QString &name, const Member **m) const + { + Q_ASSERT(m); + MemberMap::const_iterator it = members.find(name); + if (it == members.end()) { + *m = nullptr; + return false; + } + *m = &(*it); + return true; + } + + bool requiresImplicitReturnValue() const { + return contextType == ContextType::Binding || + contextType == ContextType::Eval || + contextType == ContextType::Global || contextType == ContextType::ScriptImportedByQML; + } + + void addUsedVariable(const QString &name) { + usedVariables.insert(name); + } + + bool addLocalVar(const QString &name, MemberType contextType, QQmlJS::AST::VariableScope scope, QQmlJS::AST::FunctionExpression *function = nullptr, + const QQmlJS::AST::SourceLocation &endOfInitializer = QQmlJS::AST::SourceLocation()); + + struct ResolvedName { + enum Type { + Unresolved, + Global, + Local, + Stack, + Import + }; + Type type = Unresolved; + bool isArgOrEval = false; + bool isConst = false; + bool requiresTDZCheck = false; + int scope = -1; + int index = -1; + QQmlJS::AST::SourceLocation endOfDeclarationLocation; + bool isValid() const { return type != Unresolved; } + }; + ResolvedName resolveName(const QString &name, const QQmlJS::AST::SourceLocation &accessLocation); + void emitBlockHeader(Compiler::Codegen *codegen); + void emitBlockFooter(Compiler::Codegen *codegen); + + void setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator); + + bool canHaveTailCalls() const + { + if (!isStrict) + return false; + if (contextType == ContextType::Function) + return !isGenerator; + if (contextType == ContextType::Block && parent) + return parent->canHaveTailCalls(); + return false; + } + + bool canUseTracingJit() const; +}; + + +} } // namespace QV4::Compiler + +QT_END_NAMESPACE + +#endif // QV4CODEGEN_P_H diff --git a/src/qml/compiler/qv4compilercontrolflow_p.h b/src/qml/compiler/qv4compilercontrolflow_p.h new file mode 100644 index 0000000000..5b622e81d8 --- /dev/null +++ b/src/qml/compiler/qv4compilercontrolflow_p.h @@ -0,0 +1,436 @@ +/**************************************************************************** +** +** 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 QV4COMPILERCONTROLFLOW_P_H +#define QV4COMPILERCONTROLFLOW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4global_p.h> +#include <private/qv4codegen_p.h> +#include <private/qqmljsast_p.h> +#include <private/qv4bytecodegenerator_p.h> + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace Compiler { + +struct ControlFlow { + using Reference = Codegen::Reference; + using BytecodeGenerator = Moth::BytecodeGenerator; + using Instruction = Moth::Instruction; + + enum Type { + Loop, + With, + Block, + Finally, + Catch + }; + + enum UnwindType { + Break, + Continue, + Return + }; + + struct UnwindTarget { + BytecodeGenerator::Label linkLabel; + int unwindLevel; + }; + + Codegen *cg; + ControlFlow *parent; + Type type; + + ControlFlow(Codegen *cg, Type type) + : cg(cg), parent(cg->controlFlow), type(type) + { + cg->controlFlow = this; + } + + virtual ~ControlFlow() { + cg->controlFlow = parent; + } + + UnwindTarget unwindTarget(UnwindType type, const QString &label = QString()) + { + Q_ASSERT(type == Break || type == Continue || type == Return); + ControlFlow *flow = this; + int level = 0; + while (flow) { + BytecodeGenerator::Label l = flow->getUnwindTarget(type, label); + if (l.isValid()) + return UnwindTarget{l, level}; + if (flow->requiresUnwind()) + ++level; + flow = flow->parent; + } + if (type == Return) + return UnwindTarget{ cg->returnLabel(), level }; + return UnwindTarget(); + } + + virtual QString label() const { return QString(); } + + bool hasLoop() const { + const ControlFlow *flow = this; + while (flow) { + if (flow->type == Loop) + return true; + flow = flow->parent; + } + return false; + } + +protected: + virtual BytecodeGenerator::Label getUnwindTarget(UnwindType, const QString & = QString()) { + return BytecodeGenerator::Label(); + } + virtual bool requiresUnwind() { + return false; + } + +public: + BytecodeGenerator::ExceptionHandler *parentUnwindHandler() { + return parent ? parent->unwindHandler() : nullptr; + } + + virtual BytecodeGenerator::ExceptionHandler *unwindHandler() { + return parentUnwindHandler(); + } + + +protected: + QString loopLabel() const { + QString label; + if (cg->_labelledStatement) { + label = cg->_labelledStatement->label.toString(); + cg->_labelledStatement = nullptr; + } + return label; + } + BytecodeGenerator *generator() const { + return cg->bytecodeGenerator; + } +}; + +struct ControlFlowUnwind : public ControlFlow +{ + BytecodeGenerator::ExceptionHandler unwindLabel; + + ControlFlowUnwind(Codegen *cg, Type type) + : ControlFlow(cg, type) + { + } + + void setupUnwindHandler() + { + unwindLabel = generator()->newExceptionHandler(); + } + + void emitUnwindHandler() + { + Q_ASSERT(requiresUnwind()); + + Instruction::UnwindDispatch dispatch; + generator()->addInstruction(dispatch); + } + + virtual BytecodeGenerator::ExceptionHandler *unwindHandler() override { + return unwindLabel.isValid() ? &unwindLabel : parentUnwindHandler(); + } +}; + +struct ControlFlowUnwindCleanup : public ControlFlowUnwind +{ + std::function<void()> cleanup = nullptr; + + ControlFlowUnwindCleanup(Codegen *cg, std::function<void()> cleanup, Type type = Block) + : ControlFlowUnwind(cg, type), cleanup(cleanup) + { + if (cleanup) { + setupUnwindHandler(); + generator()->setUnwindHandler(&unwindLabel); + } + } + + ~ControlFlowUnwindCleanup() { + if (cleanup) { + unwindLabel.link(); + generator()->setUnwindHandler(parentUnwindHandler()); + cleanup(); + emitUnwindHandler(); + } + } + + bool requiresUnwind() override { + return cleanup != nullptr; + } +}; + +struct ControlFlowLoop : public ControlFlowUnwindCleanup +{ + QString loopLabel; + BytecodeGenerator::Label *breakLabel = nullptr; + BytecodeGenerator::Label *continueLabel = nullptr; + + ControlFlowLoop(Codegen *cg, BytecodeGenerator::Label *breakLabel, BytecodeGenerator::Label *continueLabel = nullptr, std::function<void()> cleanup = nullptr) + : ControlFlowUnwindCleanup(cg, cleanup, Loop), loopLabel(ControlFlow::loopLabel()), breakLabel(breakLabel), continueLabel(continueLabel) + { + } + + BytecodeGenerator::Label getUnwindTarget(UnwindType type, const QString &label) override { + switch (type) { + case Break: + if (breakLabel && (label.isEmpty() || label == loopLabel)) + return *breakLabel; + break; + case Continue: + if (continueLabel && (label.isEmpty() || label == loopLabel)) + return *continueLabel; + break; + default: + break; + } + return BytecodeGenerator::Label(); + } + + QString label() const override { return loopLabel; } +}; + + +struct ControlFlowWith : public ControlFlowUnwind +{ + ControlFlowWith(Codegen *cg) + : ControlFlowUnwind(cg, With) + { + setupUnwindHandler(); + + // assumes the with object is in the accumulator + Instruction::PushWithContext pushScope; + generator()->addInstruction(pushScope); + generator()->setUnwindHandler(&unwindLabel); + } + + ~ControlFlowWith() { + // emit code for unwinding + unwindLabel.link(); + + generator()->setUnwindHandler(parentUnwindHandler()); + Instruction::PopContext pop; + generator()->addInstruction(pop); + + emitUnwindHandler(); + } + + bool requiresUnwind() override { + return true; + } + + +}; + +struct ControlFlowBlock : public ControlFlowUnwind +{ + ControlFlowBlock(Codegen *cg, AST::Node *ast) + : ControlFlowUnwind(cg, Block) + { + block = cg->enterBlock(ast); + block->emitBlockHeader(cg); + + if (block->requiresExecutionContext) { + setupUnwindHandler(); + generator()->setUnwindHandler(&unwindLabel); + } + } + + virtual ~ControlFlowBlock() { + // emit code for unwinding + if (block->requiresExecutionContext) { + unwindLabel.link(); + generator()->setUnwindHandler(parentUnwindHandler()); + } + + block->emitBlockFooter(cg); + + if (block->requiresExecutionContext ) + emitUnwindHandler(); + cg->leaveBlock(); + } + + virtual bool requiresUnwind() override { + return block->requiresExecutionContext; + } + + Context *block; +}; + +struct ControlFlowCatch : public ControlFlowUnwind +{ + AST::Catch *catchExpression; + bool insideCatch = false; + BytecodeGenerator::ExceptionHandler exceptionLabel; + + ControlFlowCatch(Codegen *cg, AST::Catch *catchExpression) + : ControlFlowUnwind(cg, Catch), catchExpression(catchExpression), + exceptionLabel(generator()->newExceptionHandler()) + { + generator()->setUnwindHandler(&exceptionLabel); + } + + virtual bool requiresUnwind() override { + return true; + } + + BytecodeGenerator::ExceptionHandler *unwindHandler() override { + return insideCatch ? &unwindLabel : &exceptionLabel; + } + + ~ControlFlowCatch() { + // emit code for unwinding + insideCatch = true; + setupUnwindHandler(); + + Codegen::RegisterScope scope(cg); + + // exceptions inside the try block go here + exceptionLabel.link(); + BytecodeGenerator::Jump noException = generator()->jumpNoException(); + + Context *block = cg->enterBlock(catchExpression); + + block->emitBlockHeader(cg); + + generator()->setUnwindHandler(&unwindLabel); + + if (catchExpression->patternElement->bindingIdentifier.isEmpty()) + // destructuring pattern + cg->initializeAndDestructureBindingElement(catchExpression->patternElement, Reference::fromName(cg, QStringLiteral("@caught"))); + // skip the additional block + cg->statementList(catchExpression->statement->statements); + + // exceptions inside catch and break/return statements go here + unwindLabel.link(); + block->emitBlockFooter(cg); + + cg->leaveBlock(); + + noException.link(); + generator()->setUnwindHandler(parentUnwindHandler()); + + emitUnwindHandler(); + insideCatch = false; + } +}; + +struct ControlFlowFinally : public ControlFlowUnwind +{ + AST::Finally *finally; + bool insideFinally = false; + + ControlFlowFinally(Codegen *cg, AST::Finally *finally) + : ControlFlowUnwind(cg, Finally), finally(finally) + { + Q_ASSERT(finally != nullptr); + setupUnwindHandler(); + generator()->setUnwindHandler(&unwindLabel); + } + + virtual bool requiresUnwind() override { + return !insideFinally; + } + + BytecodeGenerator::ExceptionHandler *unwindHandler() override { + return insideFinally ? parentUnwindHandler() : ControlFlowUnwind::unwindHandler(); + } + + ~ControlFlowFinally() { + // emit code for unwinding + unwindLabel.link(); + + Codegen::RegisterScope scope(cg); + + insideFinally = true; + int returnValueTemp = -1; + if (cg->requiresReturnValue) { + returnValueTemp = generator()->newRegister(); + Instruction::MoveReg move; + move.srcReg = cg->_returnAddress; + move.destReg = returnValueTemp; + generator()->addInstruction(move); + } + int exceptionTemp = generator()->newRegister(); + Instruction::GetException instr; + generator()->addInstruction(instr); + Reference::fromStackSlot(cg, exceptionTemp).storeConsumeAccumulator(); + + generator()->setUnwindHandler(parentUnwindHandler()); + cg->statement(finally->statement); + insideFinally = false; + + if (cg->requiresReturnValue) { + Instruction::MoveReg move; + move.srcReg = returnValueTemp; + move.destReg = cg->_returnAddress; + generator()->addInstruction(move); + } + Reference::fromStackSlot(cg, exceptionTemp).loadInAccumulator(); + Instruction::SetException setException; + generator()->addInstruction(setException); + + emitUnwindHandler(); + } +}; + +} } // QV4::Compiler namespace + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp new file mode 100644 index 0000000000..e0eaa8867b --- /dev/null +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -0,0 +1,895 @@ +/**************************************************************************** +** +** 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 "qv4compilerscanfunctions_p.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QStringList> +#include <QtCore/QSet> +#include <QtCore/QBuffer> +#include <QtCore/QBitArray> +#include <QtCore/QLinkedList> +#include <QtCore/QStack> +#include <private/qqmljsast_p.h> +#include <private/qv4compilercontext_p.h> +#include <private/qv4codegen_p.h> +#include <private/qv4string_p.h> + +QT_USE_NAMESPACE +using namespace QV4; +using namespace QV4::Compiler; +using namespace QQmlJS::AST; + +ScanFunctions::ScanFunctions(Codegen *cg, const QString &sourceCode, ContextType defaultProgramType) + : _cg(cg) + , _sourceCode(sourceCode) + , _context(nullptr) + , _allowFuncDecls(true) + , defaultProgramType(defaultProgramType) +{ +} + +void ScanFunctions::operator()(Node *node) +{ + if (node) + node->accept(this); + + calcEscapingVariables(); +} + +void ScanFunctions::enterGlobalEnvironment(ContextType compilationMode) +{ + enterEnvironment(astNodeForGlobalEnvironment, compilationMode, QStringLiteral("%GlobalCode")); +} + +void ScanFunctions::enterEnvironment(Node *node, ContextType compilationMode, const QString &name) +{ + Context *c = _cg->_module->contextMap.value(node); + if (!c) + c = _cg->_module->newContext(node, _context, compilationMode); + if (!c->isStrict) + c->isStrict = _cg->_strictMode; + c->name = name; + _contextStack.append(c); + _context = c; +} + +void ScanFunctions::leaveEnvironment() +{ + _contextStack.pop(); + _context = _contextStack.isEmpty() ? nullptr : _contextStack.top(); +} + +bool ScanFunctions::preVisit(Node *ast) +{ + if (_cg->hasError) + return false; + ++_recursionDepth; + + if (_recursionDepth > 1000) { + _cg->throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Maximum statement or expression depth exceeded")); + return false; + } + + return true; +} + +void ScanFunctions::postVisit(Node *) +{ + --_recursionDepth; +} + +void ScanFunctions::checkDirectivePrologue(StatementList *ast) +{ + for (StatementList *it = ast; it; it = it->next) { + if (ExpressionStatement *expr = cast<ExpressionStatement *>(it->statement)) { + if (StringLiteral *strLit = cast<StringLiteral *>(expr->expression)) { + // Use the source code, because the StringLiteral's + // value might have escape sequences in it, which is not + // allowed. + if (strLit->literalToken.length < 2) + continue; + QStringRef str = _sourceCode.midRef(strLit->literalToken.offset + 1, strLit->literalToken.length - 2); + if (str == QLatin1String("use strict")) { + _context->isStrict = true; + } else { + // TODO: give a warning. + } + continue; + } + } + + break; + } +} + +void ScanFunctions::checkName(const QStringRef &name, const SourceLocation &loc) +{ + if (_context->isStrict) { + if (name == QLatin1String("implements") + || name == QLatin1String("interface") + || name == QLatin1String("let") + || name == QLatin1String("package") + || name == QLatin1String("private") + || name == QLatin1String("protected") + || name == QLatin1String("public") + || name == QLatin1String("static") + || name == QLatin1String("yield")) { + _cg->throwSyntaxError(loc, QStringLiteral("Unexpected strict mode reserved word")); + } + } +} + +bool ScanFunctions::visit(Program *ast) +{ + enterEnvironment(ast, defaultProgramType, QStringLiteral("%ProgramCode")); + checkDirectivePrologue(ast->statements); + return true; +} + +void ScanFunctions::endVisit(Program *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(ESModule *ast) +{ + enterEnvironment(ast, defaultProgramType, QStringLiteral("%ModuleCode")); + _context->isStrict = true; + return true; +} + +void ScanFunctions::endVisit(ESModule *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(ExportDeclaration *declaration) +{ + QString module; + if (declaration->fromClause) { + module = declaration->fromClause->moduleSpecifier.toString(); + if (!module.isEmpty()) + _context->moduleRequests << module; + } + + QString localNameForDefaultExport = QStringLiteral("*default*"); + + if (declaration->exportAll) { + Compiler::ExportEntry entry; + entry.moduleRequest = declaration->fromClause->moduleSpecifier.toString(); + entry.importName = QStringLiteral("*"); + entry.location = declaration->firstSourceLocation(); + _context->exportEntries << entry; + } else if (declaration->exportClause) { + for (ExportsList *it = declaration->exportClause->exportsList; it; it = it->next) { + ExportSpecifier *spec = it->exportSpecifier; + Compiler::ExportEntry entry; + if (module.isEmpty()) + entry.localName = spec->identifier.toString(); + else + entry.importName = spec->identifier.toString(); + + entry.moduleRequest = module; + entry.exportName = spec->exportedIdentifier.toString(); + entry.location = it->firstSourceLocation(); + + _context->exportEntries << entry; + } + } else if (auto *vstmt = AST::cast<AST::VariableStatement*>(declaration->variableStatementOrDeclaration)) { + QStringList boundNames; + for (VariableDeclarationList *it = vstmt->declarations; it; it = it->next) { + if (!it->declaration) + continue; + it->declaration->boundNames(&boundNames); + } + for (const QString &name: boundNames) { + Compiler::ExportEntry entry; + entry.localName = name; + entry.exportName = name; + entry.location = vstmt->firstSourceLocation(); + _context->exportEntries << entry; + } + } else if (auto *classDecl = AST::cast<AST::ClassDeclaration*>(declaration->variableStatementOrDeclaration)) { + QString name = classDecl->name.toString(); + if (!name.isEmpty()) { + Compiler::ExportEntry entry; + entry.localName = name; + entry.exportName = name; + entry.location = classDecl->firstSourceLocation(); + _context->exportEntries << entry; + if (declaration->exportDefault) + localNameForDefaultExport = entry.localName; + } + } else if (auto *fdef = declaration->variableStatementOrDeclaration->asFunctionDefinition()) { + QString functionName; + + // Only function definitions for which we enter their name into the local environment + // can result in exports. Nested expressions such as (function foo() {}) are not accessible + // as locals and can only be exported as default exports (further down). + auto ast = declaration->variableStatementOrDeclaration; + if (AST::cast<AST::ExpressionStatement*>(ast) || AST::cast<AST::FunctionDeclaration*>(ast)) + functionName = fdef->name.toString(); + + if (!functionName.isEmpty()) { + Compiler::ExportEntry entry; + entry.localName = functionName; + entry.exportName = functionName; + entry.location = fdef->firstSourceLocation(); + _context->exportEntries << entry; + if (declaration->exportDefault) + localNameForDefaultExport = entry.localName; + } + } + + if (declaration->exportDefault) { + Compiler::ExportEntry entry; + entry.localName = localNameForDefaultExport; + _context->localNameForDefaultExport = localNameForDefaultExport; + entry.exportName = QStringLiteral("default"); + entry.location = declaration->firstSourceLocation(); + _context->exportEntries << entry; + } + + return true; // scan through potential assignment expression code, etc. +} + +bool ScanFunctions::visit(ImportDeclaration *declaration) +{ + QString module; + if (declaration->fromClause) { + module = declaration->fromClause->moduleSpecifier.toString(); + if (!module.isEmpty()) + _context->moduleRequests << module; + } + + if (!declaration->moduleSpecifier.isEmpty()) + _context->moduleRequests << declaration->moduleSpecifier.toString(); + + if (ImportClause *import = declaration->importClause) { + if (!import->importedDefaultBinding.isEmpty()) { + Compiler::ImportEntry entry; + entry.moduleRequest = module; + entry.importName = QStringLiteral("default"); + entry.localName = import->importedDefaultBinding.toString(); + entry.location = declaration->firstSourceLocation(); + _context->importEntries << entry; + } + + if (import->nameSpaceImport) { + Compiler::ImportEntry entry; + entry.moduleRequest = module; + entry.importName = QStringLiteral("*"); + entry.localName = import->nameSpaceImport->importedBinding.toString(); + entry.location = declaration->firstSourceLocation(); + _context->importEntries << entry; + } + + if (import->namedImports) { + for (ImportsList *it = import->namedImports->importsList; it; it = it->next) { + Compiler::ImportEntry entry; + entry.moduleRequest = module; + entry.localName = it->importSpecifier->importedBinding.toString(); + if (!it->importSpecifier->identifier.isEmpty()) + entry.importName = it->importSpecifier->identifier.toString(); + else + entry.importName = entry.localName; + entry.location = declaration->firstSourceLocation(); + _context->importEntries << entry; + } + } + } + return false; +} + +bool ScanFunctions::visit(CallExpression *ast) +{ + if (!_context->hasDirectEval) { + if (IdentifierExpression *id = cast<IdentifierExpression *>(ast->base)) { + if (id->name == QLatin1String("eval")) { + if (_context->usesArgumentsObject == Context::ArgumentsObjectUnknown) + _context->usesArgumentsObject = Context::ArgumentsObjectUsed; + _context->hasDirectEval = true; + } + } + } + return true; +} + +bool ScanFunctions::visit(PatternElement *ast) +{ + if (!ast->isVariableDeclaration()) + return true; + + QStringList names; + ast->boundNames(&names); + + QQmlJS::AST::SourceLocation lastInitializerLocation = ast->lastSourceLocation(); + if (_context->lastBlockInitializerLocation.isValid()) + lastInitializerLocation = _context->lastBlockInitializerLocation; + + for (const QString &name : qAsConst(names)) { + if (_context->isStrict && (name == QLatin1String("eval") || name == QLatin1String("arguments"))) + _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Variable name may not be eval or arguments in strict mode")); + checkName(QStringRef(&name), ast->identifierToken); + if (name == QLatin1String("arguments")) + _context->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + if (ast->scope == VariableScope::Const && !ast->initializer && !ast->isForDeclaration && !ast->destructuringPattern()) { + _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Missing initializer in const declaration")); + return false; + } + if (!_context->addLocalVar(name, ast->initializer ? Context::VariableDefinition : Context::VariableDeclaration, ast->scope, + /*function*/nullptr, lastInitializerLocation)) { + _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Identifier %1 has already been declared").arg(name)); + return false; + } + } + return true; +} + +bool ScanFunctions::visit(IdentifierExpression *ast) +{ + checkName(ast->name, ast->identifierToken); + if (_context->usesArgumentsObject == Context::ArgumentsObjectUnknown && ast->name == QLatin1String("arguments")) + _context->usesArgumentsObject = Context::ArgumentsObjectUsed; + _context->addUsedVariable(ast->name.toString()); + return true; +} + +bool ScanFunctions::visit(ExpressionStatement *ast) +{ + if (FunctionExpression* expr = AST::cast<AST::FunctionExpression*>(ast->expression)) { + if (!_allowFuncDecls) + _cg->throwSyntaxError(expr->functionToken, QStringLiteral("conditional function or closure declaration")); + + if (!enterFunction(expr, /*enterName*/ true)) + return false; + Node::accept(expr->formals, this); + Node::accept(expr->body, this); + leaveEnvironment(); + return false; + } else { + SourceLocation firstToken = ast->firstSourceLocation(); + if (_sourceCode.midRef(firstToken.offset, firstToken.length) == QLatin1String("function")) { + _cg->throwSyntaxError(firstToken, QStringLiteral("unexpected token")); + } + } + return true; +} + +bool ScanFunctions::visit(FunctionExpression *ast) +{ + return enterFunction(ast, /*enterName*/ false); +} + +bool ScanFunctions::visit(ClassExpression *ast) +{ + enterEnvironment(ast, ContextType::Block, QStringLiteral("%Class")); + _context->isStrict = true; + _context->hasNestedFunctions = true; + if (!ast->name.isEmpty()) + _context->addLocalVar(ast->name.toString(), Context::VariableDefinition, AST::VariableScope::Const); + return true; +} + +void ScanFunctions::endVisit(ClassExpression *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(ClassDeclaration *ast) +{ + if (!ast->name.isEmpty()) + _context->addLocalVar(ast->name.toString(), Context::VariableDeclaration, AST::VariableScope::Let); + + enterEnvironment(ast, ContextType::Block, QStringLiteral("%Class")); + _context->isStrict = true; + _context->hasNestedFunctions = true; + if (!ast->name.isEmpty()) + _context->addLocalVar(ast->name.toString(), Context::VariableDefinition, AST::VariableScope::Const); + return true; +} + +void ScanFunctions::endVisit(ClassDeclaration *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(TemplateLiteral *ast) +{ + while (ast) { + if (ast->expression) + Node::accept(ast->expression, this); + ast = ast->next; + } + return true; + +} + +bool ScanFunctions::visit(SuperLiteral *) +{ + Context *c = _context; + bool needContext = false; + while (c && (c->contextType == ContextType::Block || c->isArrowFunction)) { + needContext |= c->isArrowFunction; + c = c->parent; + } + + c->requiresExecutionContext |= needContext; + + return false; +} + +bool ScanFunctions::visit(FieldMemberExpression *ast) +{ + if (AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(ast->base)) { + if (id->name == QLatin1String("new")) { + // new.target + if (ast->name != QLatin1String("target")) { + _cg->throwSyntaxError(ast->identifierToken, QLatin1String("Expected 'target' after 'new.'.")); + return false; + } + Context *c = _context; + bool needContext = false; + while (c->contextType == ContextType::Block || c->isArrowFunction) { + needContext |= c->isArrowFunction; + c = c->parent; + } + c->requiresExecutionContext |= needContext; + c->innerFunctionAccessesNewTarget |= needContext; + + return false; + } + } + + return true; +} + +bool ScanFunctions::visit(ArrayPattern *ast) +{ + for (PatternElementList *it = ast->elements; it; it = it->next) + Node::accept(it->element, this); + + return false; +} + +bool ScanFunctions::enterFunction(FunctionExpression *ast, bool enterName) +{ + if (_context->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) + _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Function name may not be eval or arguments in strict mode")); + return enterFunction(ast, ast->name.toString(), ast->formals, ast->body, enterName); +} + +void ScanFunctions::endVisit(FunctionExpression *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(ObjectPattern *ast) +{ + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true); + Node::accept(ast->properties, this); + return false; +} + +bool ScanFunctions::visit(PatternProperty *ast) +{ + Q_UNUSED(ast); + // ### Shouldn't be required anymore +// if (ast->type == PatternProperty::Getter || ast->type == PatternProperty::Setter) { +// TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true); +// return enterFunction(ast, QString(), ast->formals, ast->functionBody, /*enterName */ false); +// } + return true; +} + +void ScanFunctions::endVisit(PatternProperty *) +{ + // ### +// if (ast->type == PatternProperty::Getter || ast->type == PatternProperty::Setter) +// leaveEnvironment(); +} + +bool ScanFunctions::visit(FunctionDeclaration *ast) +{ + return enterFunction(ast, /*enterName*/ true); +} + +void ScanFunctions::endVisit(FunctionDeclaration *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(DoWhileStatement *ast) { + { + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); + Node::accept(ast->statement, this); + } + Node::accept(ast->expression, this); + return false; +} + +bool ScanFunctions::visit(ForStatement *ast) { + enterEnvironment(ast, ContextType::Block, QStringLiteral("%For")); + Node::accept(ast->initialiser, this); + Node::accept(ast->declarations, this); + Node::accept(ast->condition, this); + Node::accept(ast->expression, this); + + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); + Node::accept(ast->statement, this); + + return false; +} + +void ScanFunctions::endVisit(ForStatement *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(ForEachStatement *ast) { + enterEnvironment(ast, ContextType::Block, QStringLiteral("%Foreach")); + if (ast->expression) + _context->lastBlockInitializerLocation = ast->expression->lastSourceLocation(); + Node::accept(ast->lhs, this); + Node::accept(ast->expression, this); + + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); + Node::accept(ast->statement, this); + + return false; +} + +void ScanFunctions::endVisit(ForEachStatement *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(ThisExpression *) +{ + _context->usesThis = true; + return false; +} + +bool ScanFunctions::visit(Block *ast) +{ + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _context->isStrict ? false : _allowFuncDecls); + enterEnvironment(ast, ContextType::Block, QStringLiteral("%Block")); + Node::accept(ast->statements, this); + return false; +} + +void ScanFunctions::endVisit(Block *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(CaseBlock *ast) +{ + enterEnvironment(ast, ContextType::Block, QStringLiteral("%CaseBlock")); + return true; +} + +void ScanFunctions::endVisit(CaseBlock *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(Catch *ast) +{ + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _context->isStrict ? false : _allowFuncDecls); + enterEnvironment(ast, ContextType::Block, QStringLiteral("%CatchBlock")); + _context->isCatchBlock = true; + QString caughtVar = ast->patternElement->bindingIdentifier.toString(); + if (caughtVar.isEmpty()) + caughtVar = QStringLiteral("@caught"); + _context->addLocalVar(caughtVar, Context::MemberType::VariableDefinition, VariableScope::Let); + + _context->caughtVariable = caughtVar; + if (_context->isStrict && + (caughtVar == QLatin1String("eval") || caughtVar == QLatin1String("arguments"))) { + _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Catch variable name may not be eval or arguments in strict mode")); + return false; + } + Node::accept(ast->patternElement, this); + // skip the block statement + Node::accept(ast->statement->statements, this); + return false; +} + +void ScanFunctions::endVisit(Catch *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(WithStatement *ast) +{ + Node::accept(ast->expression, this); + + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _context->isStrict ? false : _allowFuncDecls); + enterEnvironment(ast, ContextType::Block, QStringLiteral("%WithBlock")); + _context->isWithBlock = true; + + if (_context->isStrict) { + _cg->throwSyntaxError(ast->withToken, QStringLiteral("'with' statement is not allowed in strict mode")); + return false; + } + Node::accept(ast->statement, this); + + return false; +} + +void ScanFunctions::endVisit(WithStatement *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::enterFunction(Node *ast, const QString &name, FormalParameterList *formals, StatementList *body, bool enterName) +{ + Context *outerContext = _context; + enterEnvironment(ast, ContextType::Function, name); + + FunctionExpression *expr = AST::cast<FunctionExpression *>(ast); + if (!expr) + expr = AST::cast<FunctionDeclaration *>(ast); + if (outerContext) { + outerContext->hasNestedFunctions = true; + // The identifier of a function expression cannot be referenced from the enclosing environment. + if (enterName) { + if (!outerContext->addLocalVar(name, Context::FunctionDefinition, VariableScope::Var, expr)) { + _cg->throwSyntaxError(ast->firstSourceLocation(), QStringLiteral("Identifier %1 has already been declared").arg(name)); + return false; + } + outerContext->addLocalVar(name, Context::FunctionDefinition, VariableScope::Var, expr); + } + if (name == QLatin1String("arguments")) + outerContext->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + } + + _context->name = name; + if (formals && formals->containsName(QStringLiteral("arguments"))) + _context->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + if (expr) { + if (expr->isArrowFunction) + _context->isArrowFunction = true; + else if (expr->isGenerator) + _context->isGenerator = true; + } + + + if (!enterName && (!name.isEmpty() && (!formals || !formals->containsName(name)))) + _context->addLocalVar(name, Context::ThisFunctionName, VariableScope::Var); + _context->formals = formals; + + if (body && !_context->isStrict) + checkDirectivePrologue(body); + + bool isSimpleParameterList = formals && formals->isSimpleParameterList(); + + _context->arguments = formals ? formals->formals() : QStringList(); + + const QStringList boundNames = formals ? formals->boundNames() : QStringList(); + for (int i = 0; i < boundNames.size(); ++i) { + const QString &arg = boundNames.at(i); + if (_context->isStrict || !isSimpleParameterList) { + bool duplicate = (boundNames.indexOf(arg, i + 1) != -1); + if (duplicate) { + _cg->throwSyntaxError(formals->firstSourceLocation(), QStringLiteral("Duplicate parameter name '%1' is not allowed.").arg(arg)); + return false; + } + } + if (_context->isStrict) { + if (arg == QLatin1String("eval") || arg == QLatin1String("arguments")) { + _cg->throwSyntaxError(formals->firstSourceLocation(), QStringLiteral("'%1' cannot be used as parameter name in strict mode").arg(arg)); + return false; + } + } + if (!_context->arguments.contains(arg)) + _context->addLocalVar(arg, Context::VariableDefinition, VariableScope::Var); + } + return true; +} + +void ScanFunctions::calcEscapingVariables() +{ + Module *m = _cg->_module; + + for (Context *inner : qAsConst(m->contextMap)) { + if (inner->usesArgumentsObject != Context::ArgumentsObjectUsed) + continue; + if (inner->contextType != ContextType::Block && !inner->isArrowFunction) + continue; + Context *c = inner->parent; + while (c && (c->contextType == ContextType::Block || c->isArrowFunction)) + c = c->parent; + if (c) + c->usesArgumentsObject = Context::ArgumentsObjectUsed; + inner->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + } + for (Context *inner : qAsConst(m->contextMap)) { + if (!inner->parent || inner->usesArgumentsObject == Context::ArgumentsObjectUnknown) + inner->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + if (inner->usesArgumentsObject == Context::ArgumentsObjectUsed) { + QString arguments = QStringLiteral("arguments"); + inner->addLocalVar(arguments, Context::VariableDeclaration, AST::VariableScope::Var); + if (!inner->isStrict) { + inner->argumentsCanEscape = true; + inner->requiresExecutionContext = true; + } + } + } + + for (Context *c : qAsConst(m->contextMap)) { + if (c->contextType != ContextType::ESModule) + continue; + for (const auto &entry: c->exportEntries) { + auto m = c->members.find(entry.localName); + if (m != c->members.end()) + m->canEscape = true; + } + break; + } + + for (Context *inner : qAsConst(m->contextMap)) { + for (const QString &var : qAsConst(inner->usedVariables)) { + Context *c = inner; + while (c) { + Context *current = c; + c = c->parent; + if (current->isWithBlock || current->contextType != ContextType::Block) + break; + } + Q_ASSERT(c != inner); + while (c) { + Context::MemberMap::const_iterator it = c->members.find(var); + if (it != c->members.end()) { + if (c->parent || it->isLexicallyScoped()) { + it->canEscape = true; + c->requiresExecutionContext = true; + } else if (c->contextType == ContextType::ESModule) { + // Module instantiation provides a context, but vars used from inner + // scopes need to be stored in its locals[]. + it->canEscape = true; + } + break; + } + if (c->findArgument(var) != -1) { + c->argumentsCanEscape = true; + c->requiresExecutionContext = true; + break; + } + c = c->parent; + } + } + if (inner->hasDirectEval) { + inner->hasDirectEval = false; + inner->innerFunctionAccessesNewTarget = true; + if (!inner->isStrict) { + Context *c = inner; + while (c->contextType == ContextType::Block) { + c = c->parent; + } + Q_ASSERT(c); + c->hasDirectEval = true; + c->innerFunctionAccessesThis = true; + } + Context *c = inner; + while (c) { + c->allVarsEscape = true; + c = c->parent; + } + } + if (inner->usesThis) { + inner->usesThis = false; + bool innerFunctionAccessesThis = false; + Context *c = inner; + while (c->contextType == ContextType::Block || c->isArrowFunction) { + innerFunctionAccessesThis |= c->isArrowFunction; + c = c->parent; + } + Q_ASSERT(c); + if (!inner->isStrict) + c->usesThis = true; + c->innerFunctionAccessesThis |= innerFunctionAccessesThis; + } + } + for (Context *c : qAsConst(m->contextMap)) { + if (c->innerFunctionAccessesThis) { + // add an escaping 'this' variable + c->addLocalVar(QStringLiteral("this"), Context::VariableDefinition, VariableScope::Let); + c->requiresExecutionContext = true; + auto m = c->members.find(QStringLiteral("this")); + m->canEscape = true; + } + if (c->innerFunctionAccessesNewTarget) { + // add an escaping 'new.target' variable + c->addLocalVar(QStringLiteral("new.target"), Context::VariableDefinition, VariableScope::Let); + c->requiresExecutionContext = true; + auto m = c->members.find(QStringLiteral("new.target")); + m->canEscape = true; + } + if (c->allVarsEscape && c->contextType == ContextType::Block && c->members.isEmpty()) + c->allVarsEscape = false; + if (c->contextType == ContextType::Global || c->contextType == ContextType::ScriptImportedByQML || (!c->isStrict && c->contextType == ContextType::Eval) || m->debugMode) + c->allVarsEscape = true; + if (c->allVarsEscape) { + if (c->parent) { + c->requiresExecutionContext = true; + c->argumentsCanEscape = true; + } else { + for (const auto &m : qAsConst(c->members)) { + if (m.isLexicallyScoped()) { + c->requiresExecutionContext = true; + break; + } + } + } + } + if (c->contextType == ContextType::Block && c->isCatchBlock) { + c->requiresExecutionContext = true; + auto m = c->members.find(c->caughtVariable); + m->canEscape = true; + } + const QLatin1String exprForOn("expression for on"); + if (c->contextType == ContextType::Binding && c->name.length() > exprForOn.size() && + c->name.startsWith(exprForOn) && c->name.at(exprForOn.size()).isUpper()) + // we don't really need this for bindings, but we do for signal handlers, and in this case, + // we don't know if the code is a signal handler or not. + c->requiresExecutionContext = true; + if (c->allVarsEscape) { + for (auto &m : c->members) + m.canEscape = true; + } + } + + static const bool showEscapingVars = qEnvironmentVariableIsSet("QV4_SHOW_ESCAPING_VARS"); + if (showEscapingVars) { + qDebug() << "==== escaping variables ===="; + for (Context *c : qAsConst(m->contextMap)) { + qDebug() << "Context" << c << c->name << "requiresExecutionContext" << c->requiresExecutionContext << "isStrict" << c->isStrict; + qDebug() << " isArrowFunction" << c->isArrowFunction << "innerFunctionAccessesThis" << c->innerFunctionAccessesThis; + qDebug() << " parent:" << c->parent; + if (c->argumentsCanEscape) + qDebug() << " Arguments escape"; + for (auto it = c->members.constBegin(); it != c->members.constEnd(); ++it) { + qDebug() << " " << it.key() << it.value().index << it.value().canEscape << "isLexicallyScoped:" << it.value().isLexicallyScoped(); + } + } + } +} diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h new file mode 100644 index 0000000000..28ad846bcd --- /dev/null +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** 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 QV4COMPILERSCANFUNCTIONS_P_H +#define QV4COMPILERSCANFUNCTIONS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qv4global_p.h" +#include <private/qqmljsastvisitor_p.h> +#include <private/qqmljsast_p.h> +#include <private/qqmljsengine_p.h> +#include <private/qv4compilercontext_p.h> +#include <private/qv4util_p.h> +#include <QtCore/QStringList> +#include <QStack> +#include <QScopedValueRollback> + +QT_BEGIN_NAMESPACE + +using namespace QQmlJS; + +namespace QV4 { + +namespace Moth { +struct Instruction; +} + +namespace CompiledData { +struct CompilationUnit; +} + +namespace Compiler { + +class Codegen; + +class ScanFunctions: protected QQmlJS::AST::Visitor +{ + typedef QScopedValueRollback<bool> TemporaryBoolAssignment; +public: + ScanFunctions(Codegen *cg, const QString &sourceCode, ContextType defaultProgramType); + void operator()(AST::Node *node); + + void enterGlobalEnvironment(ContextType compilationMode); + void enterEnvironment(AST::Node *node, ContextType compilationMode, const QString &name); + void leaveEnvironment(); + + void enterQmlFunction(AST::FunctionDeclaration *ast) + { enterFunction(ast, false); } + +protected: + using Visitor::visit; + using Visitor::endVisit; + + bool preVisit(AST::Node *ast) override; + void postVisit(AST::Node *) override; + + void checkDirectivePrologue(AST::StatementList *ast); + + void checkName(const QStringRef &name, const AST::SourceLocation &loc); + + bool visit(AST::Program *ast) override; + void endVisit(AST::Program *) override; + + bool visit(AST::ESModule *ast) override; + void endVisit(AST::ESModule *) override; + + bool visit(AST::ExportDeclaration *declaration) override; + bool visit(AST::ImportDeclaration *declaration) override; + + bool visit(AST::CallExpression *ast) override; + bool visit(AST::PatternElement *ast) override; + bool visit(AST::IdentifierExpression *ast) override; + bool visit(AST::ExpressionStatement *ast) override; + bool visit(AST::FunctionExpression *ast) override; + bool visit(AST::TemplateLiteral *ast) override; + bool visit(AST::SuperLiteral *) override; + bool visit(AST::FieldMemberExpression *) override; + bool visit(AST::ArrayPattern *) override; + + bool enterFunction(AST::FunctionExpression *ast, bool enterName); + + void endVisit(AST::FunctionExpression *) override; + + bool visit(AST::ObjectPattern *ast) override; + + bool visit(AST::PatternProperty *ast) override; + void endVisit(AST::PatternProperty *) override; + + bool visit(AST::FunctionDeclaration *ast) override; + void endVisit(AST::FunctionDeclaration *) override; + + bool visit(AST::ClassExpression *ast) override; + void endVisit(AST::ClassExpression *) override; + + bool visit(AST::ClassDeclaration *ast) override; + void endVisit(AST::ClassDeclaration *) override; + + bool visit(AST::DoWhileStatement *ast) override; + bool visit(AST::ForStatement *ast) override; + void endVisit(AST::ForStatement *) override; + bool visit(AST::ForEachStatement *ast) override; + void endVisit(AST::ForEachStatement *) override; + + bool visit(AST::ThisExpression *ast) override; + + bool visit(AST::Block *ast) override; + void endVisit(AST::Block *ast) override; + + bool visit(AST::CaseBlock *ast) override; + void endVisit(AST::CaseBlock *ast) override; + + bool visit(AST::Catch *ast) override; + void endVisit(AST::Catch *ast) override; + + bool visit(AST::WithStatement *ast) override; + void endVisit(AST::WithStatement *ast) override; + +protected: + bool enterFunction(AST::Node *ast, const QString &name, AST::FormalParameterList *formals, AST::StatementList *body, bool enterName); + + void calcEscapingVariables(); +// fields: + Codegen *_cg; + const QString _sourceCode; + Context *_context; + QStack<Context *> _contextStack; + + bool _allowFuncDecls; + ContextType defaultProgramType; + + unsigned _recursionDepth = 0; + +private: + static constexpr AST::Node *astNodeForGlobalEnvironment = nullptr; +}; + +} + +} + +QT_END_NAMESPACE + +#endif // QV4CODEGEN_P_H diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index cf8cf623bc..c0b1be1492 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -38,17 +38,711 @@ ****************************************************************************/ #include "qv4instr_moth_p.h" +#include <private/qv4compileddata_p.h> +#include <private/qv4stackframe_p.h> using namespace QV4; using namespace QV4::Moth; -int Instr::size(Type type) +int InstrInfo::size(Instr::Type type) { -#define MOTH_RETURN_INSTR_SIZE(I, FMT) case I: return InstrMeta<(int)I>::Size; +#define MOTH_RETURN_INSTR_SIZE(I) case Instr::Type::I: case Instr::Type::I##_Wide: return InstrMeta<int(Instr::Type::I)>::Size; switch (type) { - FOR_EACH_MOTH_INSTR(MOTH_RETURN_INSTR_SIZE) - default: return 0; + FOR_EACH_MOTH_INSTR_ALL(MOTH_RETURN_INSTR_SIZE) } #undef MOTH_RETURN_INSTR_SIZE + Q_UNREACHABLE(); } +static QByteArray alignedNumber(int n) { + QByteArray number = QByteArray::number(n); + while (number.size() < 8) + number.prepend(' '); + return number; +} + +static QByteArray alignedLineNumber(int line) { + if (line > 0) + return alignedNumber(static_cast<int>(line)); + return QByteArray(" "); +} + +static QByteArray rawBytes(const char *data, int n) +{ + QByteArray ba; + while (n) { + uint num = *reinterpret_cast<const uchar *>(data); + if (num < 16) + ba += '0'; + ba += QByteArray::number(num, 16) + " "; + ++data; + --n; + } + while (ba.size() < 25) + ba += ' '; + return ba; +} + +static QString toString(QV4::ReturnedValue v) +{ +#ifdef V4_BOOTSTRAP + return QStringLiteral("string-const(%1)").arg(v); +#else // !V4_BOOTSTRAP + Value val = Value::fromReturnedValue(v); + QString result; + if (val.isInt32()) + result = QLatin1String("int "); + else if (val.isDouble()) + result = QLatin1String("double "); + if (val.isEmpty()) + result += QLatin1String("empty"); + else + result += val.toQStringNoThrow(); + return result; +#endif // V4_BOOTSTRAP +} + +#define ABSOLUTE_OFFSET() \ + (code - start + offset) + +#define MOTH_BEGIN_INSTR(instr) \ + { \ + INSTR_##instr(MOTH_DECODE_WITH_BASE) \ + QDebug d = qDebug(); \ + d.noquote(); \ + d.nospace(); \ + if (static_cast<int>(Instr::Type::instr) >= 0x100) \ + --base_ptr; \ + d << alignedLineNumber(line) << alignedNumber(codeOffset).constData() << ": " \ + << rawBytes(base_ptr, int(code - base_ptr)) << #instr << " "; + +#define MOTH_END_INSTR(instr) \ + continue; \ + } + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace Moth { + +const int InstrInfo::argumentCount[] = { + FOR_EACH_MOTH_INSTR_ALL(MOTH_COLLECT_NARGS) +}; + + +void dumpConstantTable(const Value *constants, uint count) +{ + QDebug d = qDebug(); + d.nospace(); + for (uint i = 0; i < count; ++i) + d << alignedNumber(int(i)).constData() << ": " + << toString(constants[i].asReturnedValue()).toUtf8().constData() << "\n"; +} + +QString dumpRegister(int reg, int nFormals) +{ + Q_STATIC_ASSERT(offsetof(CallData, function) == 0); + Q_STATIC_ASSERT(offsetof(CallData, context) == sizeof(Value)); + Q_STATIC_ASSERT(offsetof(CallData, accumulator) == 2*sizeof(Value)); + Q_STATIC_ASSERT(offsetof(CallData, thisObject) == 3*sizeof(Value)); + if (reg == CallData::Function) + return QStringLiteral("(function)"); + else if (reg == CallData::Context) + return QStringLiteral("(context)"); + else if (reg == CallData::Accumulator) + return QStringLiteral("(accumulator)"); + else if (reg == CallData::NewTarget) + return QStringLiteral("(new.target)"); + else if (reg == CallData::This) + return QStringLiteral("(this)"); + else if (reg == CallData::Argc) + return QStringLiteral("(argc)"); + reg -= CallData::HeaderSize(); + if (reg < nFormals) + return QStringLiteral("a%1").arg(reg); + reg -= nFormals; + return QStringLiteral("r%1").arg(reg); + +} + +QString dumpArguments(int argc, int argv, int nFormals) +{ + if (!argc) + return QStringLiteral("()"); + return QStringLiteral("(") + dumpRegister(argv, nFormals) + QStringLiteral(", ") + QString::number(argc) + QStringLiteral(")"); +} + +#define TRACE_SLOT QStringLiteral(" {%1}").arg(traceSlot) + +void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*startLine*/, const QVector<CompiledData::CodeOffsetToLine> &lineNumberMapping) +{ + MOTH_JUMP_TABLE; + + auto findLine = [](const CompiledData::CodeOffsetToLine &entry, uint offset) { + return entry.codeOffset < offset; + }; + + int lastLine = -1; + const char *start = code; + const char *end = code + len; + while (code < end) { + const CompiledData::CodeOffsetToLine *codeToLine = std::lower_bound(lineNumberMapping.constBegin(), lineNumberMapping.constEnd(), static_cast<uint>(code - start) + 1, findLine) - 1; + int line = int(codeToLine->line); + if (line != lastLine) + lastLine = line; + else + line = -1; + + int codeOffset = int(code - start); + + MOTH_DISPATCH() + + MOTH_BEGIN_INSTR(LoadReg) + d << dumpRegister(reg, nFormals); + MOTH_END_INSTR(LoadReg) + + MOTH_BEGIN_INSTR(StoreReg) + d << dumpRegister(reg, nFormals); + MOTH_END_INSTR(StoreReg) + + MOTH_BEGIN_INSTR(MoveReg) + d << dumpRegister(destReg, nFormals) << ", " << dumpRegister(srcReg, nFormals); + MOTH_END_INSTR(MoveReg) + + MOTH_BEGIN_INSTR(LoadImport) + d << "i" << index; + MOTH_END_INSTR(LoadImport) + + MOTH_BEGIN_INSTR(LoadConst) + d << "C" << index; + MOTH_END_INSTR(LoadConst) + + MOTH_BEGIN_INSTR(LoadNull) + MOTH_END_INSTR(LoadNull) + + MOTH_BEGIN_INSTR(LoadZero) + MOTH_END_INSTR(LoadZero) + + MOTH_BEGIN_INSTR(LoadTrue) + MOTH_END_INSTR(LoadTrue) + + MOTH_BEGIN_INSTR(LoadFalse) + MOTH_END_INSTR(LoadFalse) + + MOTH_BEGIN_INSTR(LoadUndefined) + MOTH_END_INSTR(LoadUndefined) + + MOTH_BEGIN_INSTR(LoadInt) + d << value; + MOTH_END_INSTR(LoadInt) + + MOTH_BEGIN_INSTR(MoveConst) + d << dumpRegister(destTemp, nFormals) << ", C" << constIndex; + MOTH_END_INSTR(MoveConst) + + MOTH_BEGIN_INSTR(LoadLocal) + if (index < nLocals) + d << "l" << index << TRACE_SLOT; + else + d << "a" << (index - nLocals) << TRACE_SLOT; + MOTH_END_INSTR(LoadLocal) + + MOTH_BEGIN_INSTR(StoreLocal) + if (index < nLocals) + d << "l" << index; + else + d << "a" << (index - nLocals); + MOTH_END_INSTR(StoreLocal) + + MOTH_BEGIN_INSTR(LoadScopedLocal) + if (index < nLocals) + d << "l" << index << "@" << scope << TRACE_SLOT; + else + d << "a" << (index - nLocals) << "@" << scope << TRACE_SLOT; + MOTH_END_INSTR(LoadScopedLocal) + + MOTH_BEGIN_INSTR(StoreScopedLocal) + if (index < nLocals) + d << ", " << "l" << index << "@" << scope; + else + d << ", " << "a" << (index - nLocals) << "@" << scope; + MOTH_END_INSTR(StoreScopedLocal) + + MOTH_BEGIN_INSTR(LoadRuntimeString) + d << stringId; + MOTH_END_INSTR(LoadRuntimeString) + + MOTH_BEGIN_INSTR(MoveRegExp) + d << dumpRegister(destReg, nFormals) << ", " <<regExpId; + MOTH_END_INSTR(MoveRegExp) + + MOTH_BEGIN_INSTR(LoadClosure) + d << value; + MOTH_END_INSTR(LoadClosure) + + MOTH_BEGIN_INSTR(LoadName) + d << name << TRACE_SLOT; + MOTH_END_INSTR(LoadName) + + MOTH_BEGIN_INSTR(LoadGlobalLookup) + d << index << TRACE_SLOT; + MOTH_END_INSTR(LoadGlobalLookup) + + MOTH_BEGIN_INSTR(StoreNameSloppy) + d << name; + MOTH_END_INSTR(StoreNameSloppy) + + MOTH_BEGIN_INSTR(StoreNameStrict) + d << name; + MOTH_END_INSTR(StoreNameStrict) + + MOTH_BEGIN_INSTR(LoadElement) + d << dumpRegister(base, nFormals) << "[acc]" << TRACE_SLOT; + MOTH_END_INSTR(LoadElement) + + MOTH_BEGIN_INSTR(StoreElement) + d << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]" + << TRACE_SLOT; + MOTH_END_INSTR(StoreElement) + + MOTH_BEGIN_INSTR(LoadProperty) + d << "acc[" << name << "]" << TRACE_SLOT; + MOTH_END_INSTR(LoadProperty) + + MOTH_BEGIN_INSTR(GetLookup) + d << "acc(" << index << ")" << TRACE_SLOT; + MOTH_END_INSTR(GetLookup) + + MOTH_BEGIN_INSTR(StoreProperty) + d << dumpRegister(base, nFormals) << "[" << name<< "]"; + MOTH_END_INSTR(StoreProperty) + + MOTH_BEGIN_INSTR(SetLookup) + d << dumpRegister(base, nFormals) << "(" << index << ")"; + MOTH_END_INSTR(SetLookup) + + MOTH_BEGIN_INSTR(LoadSuperProperty) + d << dumpRegister(property, nFormals); + MOTH_END_INSTR(LoadSuperProperty) + + MOTH_BEGIN_INSTR(StoreSuperProperty) + d << dumpRegister(property, nFormals); + MOTH_END_INSTR(StoreSuperProperty) + + MOTH_BEGIN_INSTR(StoreScopeObjectProperty) + d << dumpRegister(base, nFormals) << "[" << propertyIndex << "]"; + MOTH_END_INSTR(StoreScopeObjectProperty) + + MOTH_BEGIN_INSTR(LoadScopeObjectProperty) + d << dumpRegister(base, nFormals) << "[" << propertyIndex << "]" << (captureRequired ? " (capture)" : " (no capture)"); + MOTH_END_INSTR(LoadScopeObjectProperty) + + MOTH_BEGIN_INSTR(StoreContextObjectProperty) + d << dumpRegister(base, nFormals) << "[" << propertyIndex << "]"; + MOTH_END_INSTR(StoreContextObjectProperty) + + MOTH_BEGIN_INSTR(LoadContextObjectProperty) + d << dumpRegister(base, nFormals) << "[" << propertyIndex << "]" << (captureRequired ? " (capture)" : " (no capture)"); + MOTH_END_INSTR(LoadContextObjectProperty) + + MOTH_BEGIN_INSTR(LoadIdObject) + d << dumpRegister(base, nFormals) << "[" << index << "]"; + MOTH_END_INSTR(LoadIdObject) + + MOTH_BEGIN_INSTR(Yield) + MOTH_END_INSTR(Yield) + + MOTH_BEGIN_INSTR(YieldStar) + MOTH_END_INSTR(YieldStar) + + MOTH_BEGIN_INSTR(Resume) + d << ABSOLUTE_OFFSET(); + MOTH_END_INSTR(Resume) + + MOTH_BEGIN_INSTR(CallValue) + d << dumpRegister(name, nFormals) << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + MOTH_END_INSTR(CallValue) + + MOTH_BEGIN_INSTR(CallWithReceiver) + d << dumpRegister(name, nFormals) << dumpRegister(thisObject, nFormals) + << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + MOTH_END_INSTR(CallWithReceiver) + + MOTH_BEGIN_INSTR(CallProperty) + d << dumpRegister(base, nFormals) << "." << name << dumpArguments(argc, argv, nFormals) + << TRACE_SLOT; + MOTH_END_INSTR(CallProperty) + + MOTH_BEGIN_INSTR(CallPropertyLookup) + d << dumpRegister(base, nFormals) << "." << lookupIndex + << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + MOTH_END_INSTR(CallPropertyLookup) + + MOTH_BEGIN_INSTR(CallElement) + d << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]" + << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + MOTH_END_INSTR(CallElement) + + MOTH_BEGIN_INSTR(CallName) + d << name << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + MOTH_END_INSTR(CallName) + + MOTH_BEGIN_INSTR(CallPossiblyDirectEval) + d << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + MOTH_END_INSTR(CallPossiblyDirectEval) + + MOTH_BEGIN_INSTR(CallGlobalLookup) + d << index << dumpArguments(argc, argv, nFormals) << TRACE_SLOT; + MOTH_END_INSTR(CallGlobalLookup) + + MOTH_BEGIN_INSTR(CallScopeObjectProperty) + d << dumpRegister(base, nFormals) << "." << name << dumpArguments(argc, argv, nFormals) + << TRACE_SLOT; + MOTH_END_INSTR(CallScopeObjectProperty) + + MOTH_BEGIN_INSTR(CallContextObjectProperty) + d << dumpRegister(base, nFormals) << "." << name << dumpArguments(argc, argv, nFormals) + << TRACE_SLOT; + MOTH_END_INSTR(CallContextObjectProperty) + + MOTH_BEGIN_INSTR(CallWithSpread) + d << "new " << dumpRegister(func, nFormals) << dumpRegister(thisObject, nFormals) + << dumpArguments(argc, argv, nFormals) + << TRACE_SLOT; + MOTH_END_INSTR(CallWithSpread) + + MOTH_BEGIN_INSTR(Construct) + d << "new " << dumpRegister(func, nFormals) << dumpArguments(argc, argv, nFormals); + MOTH_END_INSTR(Construct) + + MOTH_BEGIN_INSTR(ConstructWithSpread) + d << "new " << dumpRegister(func, nFormals) << dumpArguments(argc, argv, nFormals); + MOTH_END_INSTR(ConstructWithSpread) + + MOTH_BEGIN_INSTR(SetUnwindHandler) + if (offset) + d << ABSOLUTE_OFFSET(); + else + d << "<null>"; + MOTH_END_INSTR(SetUnwindHandler) + + MOTH_BEGIN_INSTR(UnwindDispatch) + MOTH_END_INSTR(UnwindDispatch) + + MOTH_BEGIN_INSTR(UnwindToLabel) + d << "(" << level << ") " << ABSOLUTE_OFFSET(); + MOTH_END_INSTR(UnwindToLabel) + + MOTH_BEGIN_INSTR(DeadTemporalZoneCheck) + d << name; + MOTH_END_INSTR(DeadTemporalZoneCheck) + + MOTH_BEGIN_INSTR(ThrowException) + MOTH_END_INSTR(ThrowException) + + MOTH_BEGIN_INSTR(GetException) + MOTH_END_INSTR(HasException) + + MOTH_BEGIN_INSTR(SetException) + MOTH_END_INSTR(SetExceptionFlag) + + MOTH_BEGIN_INSTR(CreateCallContext) + MOTH_END_INSTR(CreateCallContext) + + MOTH_BEGIN_INSTR(PushCatchContext) + d << index << ", " << name; + MOTH_END_INSTR(PushCatchContext) + + MOTH_BEGIN_INSTR(PushWithContext) + MOTH_END_INSTR(PushWithContext) + + MOTH_BEGIN_INSTR(PushBlockContext) + d << index; + MOTH_END_INSTR(PushBlockContext) + + MOTH_BEGIN_INSTR(CloneBlockContext) + MOTH_END_INSTR(CloneBlockContext) + + MOTH_BEGIN_INSTR(PushScriptContext) + d << index; + MOTH_END_INSTR(PushScriptContext) + + MOTH_BEGIN_INSTR(PopScriptContext) + MOTH_END_INSTR(PopScriptContext) + + MOTH_BEGIN_INSTR(PopContext) + MOTH_END_INSTR(PopContext) + + MOTH_BEGIN_INSTR(GetIterator) + d << iterator; + MOTH_END_INSTR(GetIterator) + + MOTH_BEGIN_INSTR(IteratorNext) + d << dumpRegister(value, nFormals) << ", " << dumpRegister(done, nFormals); + MOTH_END_INSTR(IteratorNext) + + MOTH_BEGIN_INSTR(IteratorNextForYieldStar) + d << dumpRegister(iterator, nFormals) << ", " << dumpRegister(object, nFormals); + MOTH_END_INSTR(IteratorNextForYieldStar) + + MOTH_BEGIN_INSTR(IteratorClose) + d << dumpRegister(done, nFormals); + MOTH_END_INSTR(IteratorClose) + + MOTH_BEGIN_INSTR(DestructureRestElement) + MOTH_END_INSTR(DestructureRestElement) + + MOTH_BEGIN_INSTR(DeleteProperty) + d << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]"; + MOTH_END_INSTR(DeleteProperty) + + MOTH_BEGIN_INSTR(DeleteName) + d << name; + MOTH_END_INSTR(DeleteName) + + MOTH_BEGIN_INSTR(TypeofName) + d << name; + MOTH_END_INSTR(TypeofName) + + MOTH_BEGIN_INSTR(TypeofValue) + MOTH_END_INSTR(TypeofValue) + + MOTH_BEGIN_INSTR(DeclareVar) + d << isDeletable << ", " << varName; + MOTH_END_INSTR(DeclareVar) + + MOTH_BEGIN_INSTR(DefineArray) + d << dumpRegister(args, nFormals) << ", " << argc; + MOTH_END_INSTR(DefineArray) + + MOTH_BEGIN_INSTR(DefineObjectLiteral) + d << internalClassId + << ", " << argc + << ", " << dumpRegister(args, nFormals); + MOTH_END_INSTR(DefineObjectLiteral) + + MOTH_BEGIN_INSTR(CreateClass) + d << classIndex + << ", " << dumpRegister(heritage, nFormals) + << ", " << dumpRegister(computedNames, nFormals); + MOTH_END_INSTR(CreateClass) + + MOTH_BEGIN_INSTR(CreateMappedArgumentsObject) + MOTH_END_INSTR(CreateMappedArgumentsObject) + + MOTH_BEGIN_INSTR(CreateUnmappedArgumentsObject) + MOTH_END_INSTR(CreateUnmappedArgumentsObject) + + MOTH_BEGIN_INSTR(CreateRestParameter) + d << argIndex; + MOTH_END_INSTR(CreateRestParameter) + + MOTH_BEGIN_INSTR(ConvertThisToObject) + MOTH_END_INSTR(ConvertThisToObject) + + MOTH_BEGIN_INSTR(LoadSuperConstructor) + MOTH_END_INSTR(LoadSuperConstructor) + + MOTH_BEGIN_INSTR(ToObject) + MOTH_END_INSTR(ToObject) + + MOTH_BEGIN_INSTR(Jump) + d << ABSOLUTE_OFFSET(); + MOTH_END_INSTR(Jump) + + MOTH_BEGIN_INSTR(JumpTrue) + d << ABSOLUTE_OFFSET() << TRACE_SLOT; + MOTH_END_INSTR(JumpTrue) + + MOTH_BEGIN_INSTR(JumpFalse) + d << ABSOLUTE_OFFSET() << TRACE_SLOT; + MOTH_END_INSTR(JumpFalse) + + MOTH_BEGIN_INSTR(JumpNotUndefined) + d << ABSOLUTE_OFFSET(); + MOTH_END_INSTR(JumpNotUndefined) + + MOTH_BEGIN_INSTR(JumpNoException) + d << ABSOLUTE_OFFSET(); + MOTH_END_INSTR(JumpNoException) + + MOTH_BEGIN_INSTR(CmpEqNull) + MOTH_END_INSTR(CmpEqNull) + + MOTH_BEGIN_INSTR(CmpNeNull) + MOTH_END_INSTR(CmpNeNull) + + MOTH_BEGIN_INSTR(CmpEqInt) + d << lhs; + MOTH_END_INSTR(CmpEq) + + MOTH_BEGIN_INSTR(CmpNeInt) + d << lhs; + MOTH_END_INSTR(CmpNeInt) + + MOTH_BEGIN_INSTR(CmpEq) + d << dumpRegister(lhs, nFormals); + MOTH_END_INSTR(CmpEq) + + MOTH_BEGIN_INSTR(CmpNe) + d << dumpRegister(lhs, nFormals); + MOTH_END_INSTR(CmpNe) + + MOTH_BEGIN_INSTR(CmpGt) + d << dumpRegister(lhs, nFormals); + MOTH_END_INSTR(CmpGt) + + MOTH_BEGIN_INSTR(CmpGe) + d << dumpRegister(lhs, nFormals); + MOTH_END_INSTR(CmpGe) + + MOTH_BEGIN_INSTR(CmpLt) + d << dumpRegister(lhs, nFormals); + MOTH_END_INSTR(CmpLt) + + MOTH_BEGIN_INSTR(CmpLe) + d << dumpRegister(lhs, nFormals); + MOTH_END_INSTR(CmpLe) + + MOTH_BEGIN_INSTR(CmpStrictEqual) + d << dumpRegister(lhs, nFormals); + MOTH_END_INSTR(CmpStrictEqual) + + MOTH_BEGIN_INSTR(CmpStrictNotEqual) + d << dumpRegister(lhs, nFormals); + MOTH_END_INSTR(CmpStrictNotEqual) + + MOTH_BEGIN_INSTR(UNot) + MOTH_END_INSTR(UNot) + + MOTH_BEGIN_INSTR(UPlus) + MOTH_END_INSTR(UPlus) + + MOTH_BEGIN_INSTR(UMinus) + d << TRACE_SLOT; + MOTH_END_INSTR(UMinus) + + MOTH_BEGIN_INSTR(UCompl) + MOTH_END_INSTR(UCompl) + + MOTH_BEGIN_INSTR(Increment) + d << TRACE_SLOT; + MOTH_END_INSTR(Increment) + + MOTH_BEGIN_INSTR(Decrement) + d << TRACE_SLOT; + MOTH_END_INSTR(Decrement) + + MOTH_BEGIN_INSTR(Add) + d << dumpRegister(lhs, nFormals) << ", acc" << TRACE_SLOT; + MOTH_END_INSTR(Add) + + MOTH_BEGIN_INSTR(BitAnd) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(BitAnd) + + MOTH_BEGIN_INSTR(BitOr) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(BitOr) + + MOTH_BEGIN_INSTR(BitXor) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(BitXor) + + MOTH_BEGIN_INSTR(UShr) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(UShr) + + MOTH_BEGIN_INSTR(Shr) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(Shr) + + MOTH_BEGIN_INSTR(Shl) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(Shl) + + MOTH_BEGIN_INSTR(BitAndConst) + d << "acc, " << rhs; + MOTH_END_INSTR(BitAndConst) + + MOTH_BEGIN_INSTR(BitOrConst) + d << "acc, " << rhs; + MOTH_END_INSTR(BitOr) + + MOTH_BEGIN_INSTR(BitXorConst) + d << "acc, " << rhs; + MOTH_END_INSTR(BitXor) + + MOTH_BEGIN_INSTR(UShrConst) + d << "acc, " << rhs; + MOTH_END_INSTR(UShrConst) + + MOTH_BEGIN_INSTR(ShrConst) + d << "acc, " << rhs; + MOTH_END_INSTR(ShrConst) + + MOTH_BEGIN_INSTR(ShlConst) + d << "acc, " << rhs; + MOTH_END_INSTR(ShlConst) + + MOTH_BEGIN_INSTR(Exp) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(Exp) + + MOTH_BEGIN_INSTR(Mul) + d << dumpRegister(lhs, nFormals) << ", acc" << TRACE_SLOT; + MOTH_END_INSTR(Mul) + + MOTH_BEGIN_INSTR(Div) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(Div) + + MOTH_BEGIN_INSTR(Mod) + d << dumpRegister(lhs, nFormals) << ", acc" << TRACE_SLOT; + MOTH_END_INSTR(Mod) + + MOTH_BEGIN_INSTR(Sub) + d << dumpRegister(lhs, nFormals) << ", acc" << TRACE_SLOT; + MOTH_END_INSTR(Sub) + + MOTH_BEGIN_INSTR(CmpIn) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(CmpIn) + + MOTH_BEGIN_INSTR(CmpInstanceOf) + d << dumpRegister(lhs, nFormals) << ", acc"; + MOTH_END_INSTR(CmpInstanceOf) + + MOTH_BEGIN_INSTR(Ret) + MOTH_END_INSTR(Ret) + + MOTH_BEGIN_INSTR(Debug) + MOTH_END_INSTR(Debug) + + MOTH_BEGIN_INSTR(InitializeBlockDeadTemporalZone) + d << dumpRegister(firstReg, nFormals) << ", " << count; + MOTH_END_INSTR(InitializeBlockDeadTemporalZone) + + MOTH_BEGIN_INSTR(ThrowOnNullOrUndefined) + MOTH_END_INSTR(ThrowOnNullOrUndefined) + + MOTH_BEGIN_INSTR(GetTemplateObject) + d << index; + MOTH_END_INSTR(GetTemplateObject) + + MOTH_BEGIN_INSTR(LoadQmlContext) + d << dumpRegister(result, nFormals); + MOTH_END_INSTR(LoadQmlContext) + + MOTH_BEGIN_INSTR(LoadQmlImportedScripts) + d << dumpRegister(result, nFormals); + MOTH_END_INSTR(LoadQmlImportedScripts) + + MOTH_BEGIN_INSTR(TailCall) + d << dumpRegister(func, nFormals) << dumpRegister(thisObject, nFormals) << dumpArguments(argc, argv, nFormals); + MOTH_END_INSTR(TailCall) + } +} + +} +} +QT_END_NAMESPACE diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index dabda7bae8..3996143843 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -53,869 +53,575 @@ #include <private/qv4global_p.h> #include <private/qv4value_p.h> #include <private/qv4runtime_p.h> - -#if !defined(V4_BOOTSTRAP) -QT_REQUIRE_CONFIG(qml_interpreter); -#endif +#include <private/qv4compileddata_p.h> // for CompiledData::CodeOffsetToLine used by the dumper +#include <qendian.h> 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 INSTRUCTION(op, name, nargs, ...) \ + op##_INSTRUCTION(name, nargs, __VA_ARGS__) + +/* for all jump instructions, the offset has to come last, to simplify the job of the bytecode generator */ +#define INSTR_Nop(op) INSTRUCTION(op, Nop, 0) +#define INSTR_Ret(op) INSTRUCTION(op, Ret, 0) +#define INSTR_Debug(op) INSTRUCTION(op, Debug, 0) +#define INSTR_LoadConst(op) INSTRUCTION(op, LoadConst, 1, index) +#define INSTR_LoadZero(op) INSTRUCTION(op, LoadZero, 0) +#define INSTR_LoadTrue(op) INSTRUCTION(op, LoadTrue, 0) +#define INSTR_LoadFalse(op) INSTRUCTION(op, LoadFalse, 0) +#define INSTR_LoadNull(op) INSTRUCTION(op, LoadNull, 0) +#define INSTR_LoadUndefined(op) INSTRUCTION(op, LoadUndefined, 0) +#define INSTR_LoadInt(op) INSTRUCTION(op, LoadInt, 1, value) +#define INSTR_MoveConst(op) INSTRUCTION(op, MoveConst, 2, constIndex, destTemp) +#define INSTR_LoadReg(op) INSTRUCTION(op, LoadReg, 1, reg) +#define INSTR_StoreReg(op) INSTRUCTION(op, StoreReg, 1, reg) +#define INSTR_MoveReg(op) INSTRUCTION(op, MoveReg, 2, srcReg, destReg) +#define INSTR_LoadImport(op) INSTRUCTION(op, LoadImport, 1, index) +#define INSTR_LoadLocal(op) INSTRUCTION(op, LoadLocal, 2, index, traceSlot) +#define INSTR_StoreLocal(op) INSTRUCTION(op, StoreLocal, 1, index) +#define INSTR_LoadScopedLocal(op) INSTRUCTION(op, LoadScopedLocal, 3, scope, index, traceSlot) +#define INSTR_StoreScopedLocal(op) INSTRUCTION(op, StoreScopedLocal, 2, scope, index) +#define INSTR_LoadRuntimeString(op) INSTRUCTION(op, LoadRuntimeString, 1, stringId) +#define INSTR_MoveRegExp(op) INSTRUCTION(op, MoveRegExp, 2, regExpId, destReg) +#define INSTR_LoadClosure(op) INSTRUCTION(op, LoadClosure, 1, value) +#define INSTR_LoadName(op) INSTRUCTION(op, LoadName, 2, name, traceSlot) +#define INSTR_LoadGlobalLookup(op) INSTRUCTION(op, LoadGlobalLookup, 2, index, traceSlot) +#define INSTR_StoreNameSloppy(op) INSTRUCTION(op, StoreNameSloppy, 1, name) +#define INSTR_StoreNameStrict(op) INSTRUCTION(op, StoreNameStrict, 1, name) +#define INSTR_LoadProperty(op) INSTRUCTION(op, LoadProperty, 2, name, traceSlot) +#define INSTR_GetLookup(op) INSTRUCTION(op, GetLookup, 2, index, traceSlot) +#define INSTR_LoadScopeObjectProperty(op) INSTRUCTION(op, LoadScopeObjectProperty, 3, propertyIndex, base, captureRequired) +#define INSTR_LoadContextObjectProperty(op) INSTRUCTION(op, LoadContextObjectProperty, 3, propertyIndex, base, captureRequired) +#define INSTR_LoadIdObject(op) INSTRUCTION(op, LoadIdObject, 2, index, base) +#define INSTR_Yield(op) INSTRUCTION(op, Yield, 0) +#define INSTR_YieldStar(op) INSTRUCTION(op, YieldStar, 0) +#define INSTR_Resume(op) INSTRUCTION(op, Resume, 1, offset) +#define INSTR_IteratorNextForYieldStar(op) INSTRUCTION(op, IteratorNextForYieldStar, 2, iterator, object) +#define INSTR_StoreProperty(op) INSTRUCTION(op, StoreProperty, 2, name, base) +#define INSTR_SetLookup(op) INSTRUCTION(op, SetLookup, 2, index, base) +#define INSTR_LoadSuperProperty(op) INSTRUCTION(op, LoadSuperProperty, 1, property) +#define INSTR_StoreSuperProperty(op) INSTRUCTION(op, StoreSuperProperty, 1, property) +#define INSTR_StoreScopeObjectProperty(op) INSTRUCTION(op, StoreScopeObjectProperty, 2, base, propertyIndex) +#define INSTR_StoreContextObjectProperty(op) INSTRUCTION(op, StoreContextObjectProperty, 2, base, propertyIndex) +#define INSTR_LoadElement(op) INSTRUCTION(op, LoadElement, 2, base, traceSlot) +#define INSTR_StoreElement(op) INSTRUCTION(op, StoreElement, 3, base, index, traceSlot) +#define INSTR_CallValue(op) INSTRUCTION(op, CallValue, 4, name, argc, argv, traceSlot) +#define INSTR_CallWithReceiver(op) INSTRUCTION(op, CallWithReceiver, 5, name, thisObject, argc, argv, traceSlot) +#define INSTR_CallProperty(op) INSTRUCTION(op, CallProperty, 5, name, base, argc, argv, traceSlot) +#define INSTR_CallPropertyLookup(op) INSTRUCTION(op, CallPropertyLookup, 5, lookupIndex, base, argc, argv, traceSlot) +#define INSTR_CallElement(op) INSTRUCTION(op, CallElement, 5, base, index, argc, argv, traceSlot) +#define INSTR_CallName(op) INSTRUCTION(op, CallName, 4, name, argc, argv, traceSlot) +#define INSTR_CallPossiblyDirectEval(op) INSTRUCTION(op, CallPossiblyDirectEval, 3, argc, argv, traceSlot) +#define INSTR_CallGlobalLookup(op) INSTRUCTION(op, CallGlobalLookup, 4, index, argc, argv, traceSlot) +#define INSTR_CallScopeObjectProperty(op) INSTRUCTION(op, CallScopeObjectProperty, 5, name, base, argc, argv, traceSlot) +#define INSTR_CallContextObjectProperty(op) INSTRUCTION(op, CallContextObjectProperty, 5, name, base, argc, argv, traceSlot) +#define INSTR_CallWithSpread(op) INSTRUCTION(op, CallWithSpread, 5, func, thisObject, argc, argv, traceSlot) +#define INSTR_Construct(op) INSTRUCTION(op, Construct, 3, func, argc, argv) +#define INSTR_ConstructWithSpread(op) INSTRUCTION(op, ConstructWithSpread, 3, func, argc, argv) +#define INSTR_SetUnwindHandler(op) INSTRUCTION(op, SetUnwindHandler, 1, offset) +#define INSTR_UnwindDispatch(op) INSTRUCTION(op, UnwindDispatch, 0) +#define INSTR_UnwindToLabel(op) INSTRUCTION(op, UnwindToLabel, 2, level, offset) +#define INSTR_DeadTemporalZoneCheck(op) INSTRUCTION(op, DeadTemporalZoneCheck, 1, name) +#define INSTR_ThrowException(op) INSTRUCTION(op, ThrowException, 0) +#define INSTR_GetException(op) INSTRUCTION(op, GetException, 0) +#define INSTR_SetException(op) INSTRUCTION(op, SetException, 0) +#define INSTR_CreateCallContext(op) INSTRUCTION(op, CreateCallContext, 0) +#define INSTR_PushCatchContext(op) INSTRUCTION(op, PushCatchContext, 2, index, name) +#define INSTR_PushWithContext(op) INSTRUCTION(op, PushWithContext, 0) +#define INSTR_PushBlockContext(op) INSTRUCTION(op, PushBlockContext, 1, index) +#define INSTR_CloneBlockContext(op) INSTRUCTION(op, CloneBlockContext, 0) +#define INSTR_PushScriptContext(op) INSTRUCTION(op, PushScriptContext, 1, index) +#define INSTR_PopScriptContext(op) INSTRUCTION(op, PopScriptContext, 0) +#define INSTR_PopContext(op) INSTRUCTION(op, PopContext, 0) +#define INSTR_GetIterator(op) INSTRUCTION(op, GetIterator, 1, iterator) +#define INSTR_IteratorNext(op) INSTRUCTION(op, IteratorNext, 2, value, done) +#define INSTR_IteratorClose(op) INSTRUCTION(op, IteratorClose, 1, done) +#define INSTR_DestructureRestElement(op) INSTRUCTION(op, DestructureRestElement, 0) +#define INSTR_DeleteProperty(op) INSTRUCTION(op, DeleteProperty, 2, base, index) +#define INSTR_DeleteName(op) INSTRUCTION(op, DeleteName, 1, name) +#define INSTR_TypeofName(op) INSTRUCTION(op, TypeofName, 1, name) +#define INSTR_TypeofValue(op) INSTRUCTION(op, TypeofValue, 0) +#define INSTR_DeclareVar(op) INSTRUCTION(op, DeclareVar, 2, varName, isDeletable) +#define INSTR_DefineArray(op) INSTRUCTION(op, DefineArray, 2, argc, args) +#define INSTR_DefineObjectLiteral(op) INSTRUCTION(op, DefineObjectLiteral, 3, internalClassId, argc, args) +#define INSTR_CreateClass(op) INSTRUCTION(op, CreateClass, 3, classIndex, heritage, computedNames) +#define INSTR_CreateMappedArgumentsObject(op) INSTRUCTION(op, CreateMappedArgumentsObject, 0) +#define INSTR_CreateUnmappedArgumentsObject(op) INSTRUCTION(op, CreateUnmappedArgumentsObject, 0) +#define INSTR_CreateRestParameter(op) INSTRUCTION(op, CreateRestParameter, 1, argIndex) +#define INSTR_ConvertThisToObject(op) INSTRUCTION(op, ConvertThisToObject, 0) +#define INSTR_LoadSuperConstructor(op) INSTRUCTION(op, LoadSuperConstructor, 0) +#define INSTR_ToObject(op) INSTRUCTION(op, ToObject, 0) +#define INSTR_Jump(op) INSTRUCTION(op, Jump, 1, offset) +#define INSTR_JumpTrue(op) INSTRUCTION(op, JumpTrue, 2, traceSlot, offset) +#define INSTR_JumpFalse(op) INSTRUCTION(op, JumpFalse, 2, traceSlot, offset) +#define INSTR_JumpNotUndefined(op) INSTRUCTION(op, JumpNotUndefined, 1, offset) +#define INSTR_JumpNoException(op) INSTRUCTION(op, JumpNoException, 1, offset) +#define INSTR_CmpEqNull(op) INSTRUCTION(op, CmpEqNull, 0) +#define INSTR_CmpNeNull(op) INSTRUCTION(op, CmpNeNull, 0) +#define INSTR_CmpEqInt(op) INSTRUCTION(op, CmpEqInt, 1, lhs) +#define INSTR_CmpNeInt(op) INSTRUCTION(op, CmpNeInt, 1, lhs) +#define INSTR_CmpEq(op) INSTRUCTION(op, CmpEq, 1, lhs) +#define INSTR_CmpNe(op) INSTRUCTION(op, CmpNe, 1, lhs) +#define INSTR_CmpGt(op) INSTRUCTION(op, CmpGt, 1, lhs) +#define INSTR_CmpGe(op) INSTRUCTION(op, CmpGe, 1, lhs) +#define INSTR_CmpLt(op) INSTRUCTION(op, CmpLt, 1, lhs) +#define INSTR_CmpLe(op) INSTRUCTION(op, CmpLe, 1, lhs) +#define INSTR_CmpStrictEqual(op) INSTRUCTION(op, CmpStrictEqual, 1, lhs) +#define INSTR_CmpStrictNotEqual(op) INSTRUCTION(op, CmpStrictNotEqual, 1, lhs) +#define INSTR_CmpIn(op) INSTRUCTION(op, CmpIn, 1, lhs) +#define INSTR_CmpInstanceOf(op) INSTRUCTION(op, CmpInstanceOf, 1, lhs) +#define INSTR_UNot(op) INSTRUCTION(op, UNot, 0) +#define INSTR_UPlus(op) INSTRUCTION(op, UPlus, 0) +#define INSTR_UMinus(op) INSTRUCTION(op, UMinus, 1, traceSlot) +#define INSTR_UCompl(op) INSTRUCTION(op, UCompl, 0) +#define INSTR_Increment(op) INSTRUCTION(op, Increment, 1, traceSlot) +#define INSTR_Decrement(op) INSTRUCTION(op, Decrement, 1, traceSlot) +#define INSTR_Add(op) INSTRUCTION(op, Add, 2, lhs, traceSlot) +#define INSTR_BitAnd(op) INSTRUCTION(op, BitAnd, 1, lhs) +#define INSTR_BitOr(op) INSTRUCTION(op, BitOr, 1, lhs) +#define INSTR_BitXor(op) INSTRUCTION(op, BitXor, 1, lhs) +#define INSTR_UShr(op) INSTRUCTION(op, UShr, 1, lhs) +#define INSTR_Shr(op) INSTRUCTION(op, Shr, 1, lhs) +#define INSTR_Shl(op) INSTRUCTION(op, Shl, 1, lhs) +#define INSTR_BitAndConst(op) INSTRUCTION(op, BitAndConst, 1, rhs) +#define INSTR_BitOrConst(op) INSTRUCTION(op, BitOrConst, 1, rhs) +#define INSTR_BitXorConst(op) INSTRUCTION(op, BitXorConst, 1, rhs) +#define INSTR_UShrConst(op) INSTRUCTION(op, UShrConst, 1, rhs) +#define INSTR_ShrConst(op) INSTRUCTION(op, ShrConst, 1, rhs) +#define INSTR_ShlConst(op) INSTRUCTION(op, ShlConst, 1, rhs) +#define INSTR_Exp(op) INSTRUCTION(op, Exp, 1, lhs) +#define INSTR_Mul(op) INSTRUCTION(op, Mul, 2, lhs, traceSlot) +#define INSTR_Div(op) INSTRUCTION(op, Div, 1, lhs) +#define INSTR_Mod(op) INSTRUCTION(op, Mod, 2, lhs, traceSlot) +#define INSTR_Sub(op) INSTRUCTION(op, Sub, 2, lhs, traceSlot) +#define INSTR_LoadQmlContext(op) INSTRUCTION(op, LoadQmlContext, 1, result) +#define INSTR_LoadQmlImportedScripts(op) INSTRUCTION(op, LoadQmlImportedScripts, 1, result) +#define INSTR_InitializeBlockDeadTemporalZone(op) INSTRUCTION(op, InitializeBlockDeadTemporalZone, 2, firstReg, count) +#define INSTR_ThrowOnNullOrUndefined(op) INSTRUCTION(op, ThrowOnNullOrUndefined, 0) +#define INSTR_GetTemplateObject(op) INSTRUCTION(op, GetTemplateObject, 1, index) +#define INSTR_TailCall(op) INSTRUCTION(op, TailCall, 4, func, thisObject, argc, argv) + +#define FOR_EACH_MOTH_INSTR_ALL(F) \ + F(Nop) \ + FOR_EACH_MOTH_INSTR(F) #define FOR_EACH_MOTH_INSTR(F) \ - F(Ret, ret) \ - MOTH_DEBUG_INSTR(F) \ - F(LoadRuntimeString, loadRuntimeString) \ - F(LoadRegExp, loadRegExp) \ - F(LoadClosure, loadClosure) \ - F(Move, move) \ - F(MoveConst, moveConst) \ - F(SwapTemps, swapTemps) \ - F(LoadName, loadName) \ - F(GetGlobalLookup, getGlobalLookup) \ - F(StoreName, storeName) \ - F(LoadElement, loadElement) \ - F(LoadElementLookup, loadElementLookup) \ - F(StoreElement, storeElement) \ - F(StoreElementLookup, storeElementLookup) \ - F(LoadProperty, loadProperty) \ - F(GetLookup, getLookup) \ - F(StoreProperty, storeProperty) \ - F(SetLookup, setLookup) \ - F(StoreQObjectProperty, storeQObjectProperty) \ - F(LoadQObjectProperty, loadQObjectProperty) \ - F(StoreScopeObjectProperty, storeScopeObjectProperty) \ - F(StoreContextObjectProperty, storeContextObjectProperty) \ - F(LoadScopeObjectProperty, loadScopeObjectProperty) \ - F(LoadContextObjectProperty, loadContextObjectProperty) \ - F(LoadIdObject, loadIdObject) \ - F(LoadAttachedQObjectProperty, loadAttachedQObjectProperty) \ - F(LoadSingletonQObjectProperty, loadQObjectProperty) \ - F(Push, push) \ - F(CallValue, callValue) \ - F(CallProperty, callProperty) \ - F(CallPropertyLookup, callPropertyLookup) \ - F(CallScopeObjectProperty, callScopeObjectProperty) \ - F(CallContextObjectProperty, callContextObjectProperty) \ - F(CallElement, callElement) \ - F(CallActivationProperty, callActivationProperty) \ - F(CallGlobalLookup, callGlobalLookup) \ - F(SetExceptionHandler, setExceptionHandler) \ - F(CallBuiltinThrow, callBuiltinThrow) \ - F(CallBuiltinUnwindException, callBuiltinUnwindException) \ - F(CallBuiltinPushCatchScope, callBuiltinPushCatchScope) \ - F(CallBuiltinPushScope, callBuiltinPushScope) \ - F(CallBuiltinPopScope, callBuiltinPopScope) \ - F(CallBuiltinForeachIteratorObject, callBuiltinForeachIteratorObject) \ - F(CallBuiltinForeachNextPropertyName, callBuiltinForeachNextPropertyName) \ - F(CallBuiltinDeleteMember, callBuiltinDeleteMember) \ - F(CallBuiltinDeleteSubscript, callBuiltinDeleteSubscript) \ - F(CallBuiltinDeleteName, callBuiltinDeleteName) \ - F(CallBuiltinTypeofScopeObjectProperty, callBuiltinTypeofScopeObjectProperty) \ - F(CallBuiltinTypeofContextObjectProperty, callBuiltinTypeofContextObjectProperty) \ - F(CallBuiltinTypeofMember, callBuiltinTypeofMember) \ - F(CallBuiltinTypeofSubscript, callBuiltinTypeofSubscript) \ - F(CallBuiltinTypeofName, callBuiltinTypeofName) \ - F(CallBuiltinTypeofValue, callBuiltinTypeofValue) \ - F(CallBuiltinDeclareVar, callBuiltinDeclareVar) \ - F(CallBuiltinDefineArray, callBuiltinDefineArray) \ - F(CallBuiltinDefineObjectLiteral, callBuiltinDefineObjectLiteral) \ - F(CallBuiltinSetupArgumentsObject, callBuiltinSetupArgumentsObject) \ - F(CallBuiltinConvertThisToObject, callBuiltinConvertThisToObject) \ - F(CreateValue, createValue) \ - F(CreateProperty, createProperty) \ - F(ConstructPropertyLookup, constructPropertyLookup) \ - F(CreateActivationProperty, createActivationProperty) \ - F(ConstructGlobalLookup, constructGlobalLookup) \ - F(Jump, jump) \ - F(JumpEq, jumpEq) \ - F(JumpNe, jumpNe) \ - F(UNot, unot) \ - F(UNotBool, unotBool) \ - F(UPlus, uplus) \ - F(UMinus, uminus) \ - F(UCompl, ucompl) \ - F(UComplInt, ucomplInt) \ - F(Increment, increment) \ - F(Decrement, decrement) \ - F(Binop, binop) \ - F(Add, add) \ - F(BitAnd, bitAnd) \ - F(BitOr, bitOr) \ - F(BitXor, bitXor) \ - F(Shr, shr) \ - F(Shl, shl) \ - F(BitAndConst, bitAndConst) \ - F(BitOrConst, bitOrConst) \ - F(BitXorConst, bitXorConst) \ - F(ShrConst, shrConst) \ - F(ShlConst, shlConst) \ - F(Mul, mul) \ - F(Sub, sub) \ - F(BinopContext, binopContext) \ - F(LoadThis, loadThis) \ - F(LoadQmlContext, loadQmlContext) \ - F(LoadQmlImportedScripts, loadQmlImportedScripts) \ - F(LoadQmlSingleton, loadQmlSingleton) - -#if defined(Q_CC_GNU) && (!defined(Q_CC_INTEL) || __INTEL_COMPILER >= 1200) -# define MOTH_THREADED_INTERPRETER + F(Ret) \ + F(LoadConst) \ + F(LoadZero) \ + F(LoadTrue) \ + F(LoadFalse) \ + F(LoadNull) \ + F(LoadUndefined) \ + F(LoadInt) \ + F(LoadRuntimeString) \ + F(MoveConst) \ + F(LoadReg) \ + F(StoreReg) \ + F(MoveReg) \ + F(LoadImport) \ + F(LoadLocal) \ + F(StoreLocal) \ + F(LoadScopedLocal) \ + F(StoreScopedLocal) \ + F(MoveRegExp) \ + F(LoadClosure) \ + F(LoadName) \ + F(LoadGlobalLookup) \ + F(StoreNameSloppy) \ + F(StoreNameStrict) \ + F(LoadElement) \ + F(StoreElement) \ + F(LoadProperty) \ + F(GetLookup) \ + F(StoreProperty) \ + F(SetLookup) \ + F(LoadSuperProperty) \ + F(StoreSuperProperty) \ + F(StoreScopeObjectProperty) \ + F(StoreContextObjectProperty) \ + F(LoadScopeObjectProperty) \ + F(LoadContextObjectProperty) \ + F(LoadIdObject) \ + F(ConvertThisToObject) \ + F(ToObject) \ + F(Jump) \ + F(JumpTrue) \ + F(JumpFalse) \ + F(JumpNoException) \ + F(JumpNotUndefined) \ + F(CmpEqNull) \ + F(CmpNeNull) \ + F(CmpEqInt) \ + F(CmpNeInt) \ + F(CmpEq) \ + F(CmpNe) \ + F(CmpGt) \ + F(CmpGe) \ + F(CmpLt) \ + F(CmpLe) \ + F(CmpStrictEqual) \ + F(CmpStrictNotEqual) \ + F(CmpIn) \ + F(CmpInstanceOf) \ + F(UNot) \ + F(UPlus) \ + F(UMinus) \ + F(UCompl) \ + F(Increment) \ + F(Decrement) \ + F(Add) \ + F(BitAnd) \ + F(BitOr) \ + F(BitXor) \ + F(UShr) \ + F(Shr) \ + F(Shl) \ + F(BitAndConst) \ + F(BitOrConst) \ + F(BitXorConst) \ + F(UShrConst) \ + F(ShrConst) \ + F(ShlConst) \ + F(Exp) \ + F(Mul) \ + F(Div) \ + F(Mod) \ + F(Sub) \ + F(CallValue) \ + F(CallWithReceiver) \ + F(CallProperty) \ + F(CallPropertyLookup) \ + F(CallElement) \ + F(CallName) \ + F(CallPossiblyDirectEval) \ + F(CallGlobalLookup) \ + F(CallScopeObjectProperty) \ + F(CallContextObjectProperty) \ + F(CallWithSpread) \ + F(Construct) \ + F(ConstructWithSpread) \ + F(SetUnwindHandler) \ + F(UnwindDispatch) \ + F(UnwindToLabel) \ + F(DeadTemporalZoneCheck) \ + F(ThrowException) \ + F(GetException) \ + F(SetException) \ + F(CreateCallContext) \ + F(PushCatchContext) \ + F(PushWithContext) \ + F(PushBlockContext) \ + F(CloneBlockContext) \ + F(PopContext) \ + F(GetIterator) \ + F(IteratorNext) \ + F(IteratorClose) \ + F(DestructureRestElement) \ + F(DeleteProperty) \ + F(DeleteName) \ + F(TypeofName) \ + F(TypeofValue) \ + F(DeclareVar) \ + F(DefineArray) \ + F(DefineObjectLiteral) \ + F(CreateMappedArgumentsObject) \ + F(CreateUnmappedArgumentsObject) \ + F(CreateRestParameter) \ + F(LoadQmlContext) \ + F(LoadQmlImportedScripts) \ + F(Yield) \ + F(YieldStar) \ + F(Resume) \ + F(IteratorNextForYieldStar) \ + F(CreateClass) \ + F(LoadSuperConstructor) \ + F(PushScriptContext) \ + F(PopScriptContext) \ + F(InitializeBlockDeadTemporalZone) \ + F(ThrowOnNullOrUndefined) \ + F(GetTemplateObject) \ + F(TailCall) \ + F(Debug) \ + +#define MOTH_NUM_INSTRUCTIONS() (static_cast<int>(Moth::Instr::Type::Debug_Wide) + 1) + +#if defined(Q_CC_GNU) +#if defined(Q_CC_INTEL) +// icc before version 1200 doesn't support computed goto, and at least up to version 18.0.0 the +// current use results in an internal compiler error. We could enable this if/when it gets fixed +// in a later version. +# elif defined(Q_OS_WASM) && !defined(__asmjs) +// Upstream llvm does not support computed goto for the wasm target, unlike the 'fastcomp' llvm fork +// shipped with the emscripten SDK. Disable computed goto usage for non-fastcomp llvm on Wasm. +#else +# define MOTH_COMPUTED_GOTO +#endif #endif #define MOTH_INSTR_ALIGN_MASK (Q_ALIGNOF(QV4::Moth::Instr) - 1) -#define MOTH_INSTR_HEADER quint32 instructionType; +#define MOTH_INSTR_ENUM(I) I, I##_Wide, +#define MOTH_INSTR_SIZE(I) (sizeof(QV4::Moth::Instr::instr_##I)) + +#define MOTH_EXPAND_FOR_MSVC(x) x +#define MOTH_DEFINE_ARGS(nargs, ...) \ + MOTH_EXPAND_FOR_MSVC(MOTH_DEFINE_ARGS##nargs(__VA_ARGS__)) + +#define MOTH_DEFINE_ARGS0() +#define MOTH_DEFINE_ARGS1(arg) \ + int arg; +#define MOTH_DEFINE_ARGS2(arg1, arg2) \ + int arg1; \ + int arg2; +#define MOTH_DEFINE_ARGS3(arg1, arg2, arg3) \ + int arg1; \ + int arg2; \ + int arg3; +#define MOTH_DEFINE_ARGS4(arg1, arg2, arg3, arg4) \ + int arg1; \ + int arg2; \ + int arg3; \ + int arg4; +#define MOTH_DEFINE_ARGS5(arg1, arg2, arg3, arg4, arg5) \ + int arg1; \ + int arg2; \ + int arg3; \ + int arg4; \ + int arg5; + +#define MOTH_COLLECT_ENUMS(instr) \ + INSTR_##instr(MOTH_GET_ENUM) +#define MOTH_GET_ENUM_INSTRUCTION(name, ...) \ + name, + +#define MOTH_EMIT_STRUCTS(instr) \ + INSTR_##instr(MOTH_EMIT_STRUCT) +#define MOTH_EMIT_STRUCT_INSTRUCTION(name, nargs, ...) \ + struct instr_##name { \ + MOTH_DEFINE_ARGS(nargs, __VA_ARGS__) \ + }; + +#define MOTH_EMIT_INSTR_MEMBERS(instr) \ + INSTR_##instr(MOTH_EMIT_INSTR_MEMBER) +#define MOTH_EMIT_INSTR_MEMBER_INSTRUCTION(name, nargs, ...) \ + instr_##name name; + +#define MOTH_COLLECT_NARGS(instr) \ + INSTR_##instr(MOTH_COLLECT_ARG_COUNT) +#define MOTH_COLLECT_ARG_COUNT_INSTRUCTION(name, nargs, ...) \ + nargs, nargs, + +#define MOTH_DECODE_ARG(arg, type, nargs, offset) \ + arg = qFromLittleEndian<type>(qFromUnaligned<type>(reinterpret_cast<const type *>(code) - nargs + offset)); +#define MOTH_ADJUST_CODE(type, nargs) \ + code += static_cast<quintptr>(nargs*sizeof(type) + 1) + +#define MOTH_DECODE_INSTRUCTION(name, nargs, ...) \ + MOTH_DEFINE_ARGS(nargs, __VA_ARGS__) \ + op_int_##name: \ + MOTH_ADJUST_CODE(int, nargs); \ + MOTH_DECODE_ARGS(name, int, nargs, __VA_ARGS__) \ + goto op_main_##name; \ + op_byte_##name: \ + MOTH_ADJUST_CODE(qint8, nargs); \ + MOTH_DECODE_ARGS(name, qint8, nargs, __VA_ARGS__) \ + op_main_##name: \ + ; \ + +#define MOTH_DECODE_WITH_BASE_INSTRUCTION(name, nargs, ...) \ + MOTH_DEFINE_ARGS(nargs, __VA_ARGS__) \ + const char *base_ptr; \ + op_int_##name: \ + base_ptr = code; \ + MOTH_ADJUST_CODE(int, nargs); \ + MOTH_DECODE_ARGS(name, int, nargs, __VA_ARGS__) \ + goto op_main_##name; \ + op_byte_##name: \ + base_ptr = code; \ + MOTH_ADJUST_CODE(qint8, nargs); \ + MOTH_DECODE_ARGS(name, qint8, nargs, __VA_ARGS__) \ + op_main_##name: \ + ; \ + +#define MOTH_DECODE_ARGS(name, type, nargs, ...) \ + MOTH_EXPAND_FOR_MSVC(MOTH_DECODE_ARGS##nargs(name, type, nargs, __VA_ARGS__)) + +#define MOTH_DECODE_ARGS0(name, type, nargs, dummy) +#define MOTH_DECODE_ARGS1(name, type, nargs, arg) \ + MOTH_DECODE_ARG(arg, type, nargs, 0); +#define MOTH_DECODE_ARGS2(name, type, nargs, arg1, arg2) \ + MOTH_DECODE_ARGS1(name, type, nargs, arg1); \ + MOTH_DECODE_ARG(arg2, type, nargs, 1); +#define MOTH_DECODE_ARGS3(name, type, nargs, arg1, arg2, arg3) \ + MOTH_DECODE_ARGS2(name, type, nargs, arg1, arg2); \ + MOTH_DECODE_ARG(arg3, type, nargs, 2); +#define MOTH_DECODE_ARGS4(name, type, nargs, arg1, arg2, arg3, arg4) \ + MOTH_DECODE_ARGS3(name, type, nargs, arg1, arg2, arg3); \ + MOTH_DECODE_ARG(arg4, type, nargs, 3); +#define MOTH_DECODE_ARGS5(name, type, nargs, arg1, arg2, arg3, arg4, arg5) \ + MOTH_DECODE_ARGS4(name, type, nargs, arg1, arg2, arg3, arg4); \ + MOTH_DECODE_ARG(arg5, type, nargs, 4); + +#ifdef MOTH_COMPUTED_GOTO +/* collect jump labels */ +#define COLLECT_LABELS(instr) \ + INSTR_##instr(GET_LABEL) \ + INSTR_##instr(GET_LABEL_WIDE) +#define GET_LABEL_INSTRUCTION(name, ...) \ + &&op_byte_##name, +#define GET_LABEL_WIDE_INSTRUCTION(name, ...) \ + &&op_int_##name, + +#define MOTH_JUMP_TABLE \ + static const void *jumpTable[] = { \ + FOR_EACH_MOTH_INSTR_ALL(COLLECT_LABELS) \ + }; + +#define MOTH_DISPATCH_SINGLE() \ + goto *jumpTable[*reinterpret_cast<const uchar *>(code)]; + +#define MOTH_DISPATCH() \ + MOTH_DISPATCH_SINGLE() \ + op_byte_Nop: \ + ++code; \ + MOTH_DISPATCH_SINGLE() \ + op_int_Nop: /* wide prefix */ \ + ++code; \ + goto *jumpTable[0x100 | *reinterpret_cast<const uchar *>(code)]; +#else +#define MOTH_JUMP_TABLE + +#define MOTH_INSTR_CASE_AND_JUMP(instr) \ + INSTR_##instr(GET_CASE_AND_JUMP) \ + INSTR_##instr(GET_CASE_AND_JUMP_WIDE) +#define GET_CASE_AND_JUMP_INSTRUCTION(name, ...) \ + case Instr::Type::name: goto op_byte_##name; +#define GET_CASE_AND_JUMP_WIDE_INSTRUCTION(name, ...) \ + case Instr::Type::name##_Wide: goto op_int_##name; + +#define MOTH_DISPATCH() \ + Instr::Type type = Instr::Type(static_cast<uchar>(*code)); \ + dispatch: \ + switch (type) { \ + case Instr::Type::Nop: \ + ++code; \ + type = Instr::Type(static_cast<uchar>(*code)); \ + goto dispatch; \ + case Instr::Type::Nop_Wide: /* wide prefix */ \ + ++code; \ + type = Instr::Type(0x100 | static_cast<uchar>(*code)); \ + goto dispatch; \ + FOR_EACH_MOTH_INSTR(MOTH_INSTR_CASE_AND_JUMP) \ + } +#endif -#define MOTH_INSTR_ENUM(I, FMT) I, -#define MOTH_INSTR_SIZE(I, FMT) ((sizeof(QV4::Moth::Instr::instr_##FMT) + MOTH_INSTR_ALIGN_MASK) & ~MOTH_INSTR_ALIGN_MASK) +namespace QV4 { +namespace CompiledData { +struct CodeOffsetToLine; +} -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 - // Temp: 1 - // Argument: 2 - // Local: 3 - // Arg(outer): 4 - // Local(outer): 5 - // ... - unsigned scope : 12; - unsigned index : 20; - - bool isConstant() const { return !scope; } - bool isArgument() const { return scope >= 2 && !(scope &1); } - bool isLocal() const { return scope == 3; } - bool isTemp() const { return scope == 1; } - bool isScopedLocal() const { return scope >= 3 && (scope & 1); } - - static Param createConstant(int index) - { - Param p; - p.scope = 0; - p.index = index; - return p; - } +class StackSlot { + int index; - static Param createArgument(unsigned idx, uint scope) - { - Param p; - p.scope = 2 + 2*scope; - p.index = idx; - return p; +public: + static StackSlot createRegister(int index) { + Q_ASSERT(index >= 0); + StackSlot t; + t.index = index; + return t; } - static Param createLocal(unsigned idx) - { - Param p; - p.scope = 3; - p.index = idx; - return p; - } - - static Param createTemp(unsigned idx) - { - Param p; - p.scope = 1; - p.index = idx; - return p; - } + int stackSlot() const { return index; } + operator int() const { return index; } +}; - static Param createScopedLocal(unsigned idx, uint scope) - { - Param p; - p.scope = 3 + 2*scope; - p.index = idx; - return p; - } +inline bool operator==(const StackSlot &l, const StackSlot &r) { return l.stackSlot() == r.stackSlot(); } +inline bool operator!=(const StackSlot &l, const StackSlot &r) { return l.stackSlot() != r.stackSlot(); } - inline bool operator==(const Param &other) const - { return scope == other.scope && index == other.index; } +// When making changes to the instructions, make sure to bump QV4_DATA_STRUCTURE_VERSION in qv4compileddata_p.h - inline bool operator!=(const Param &other) const - { return !(*this == other); } -}; +void dumpConstantTable(const Value *constants, uint count); +void dumpBytecode(const char *bytecode, int len, int nLocals, int nFormals, int startLine = 1, + const QVector<CompiledData::CodeOffsetToLine> &lineNumberMapping = QVector<CompiledData::CodeOffsetToLine>()); +inline void dumpBytecode(const QByteArray &bytecode, int nLocals, int nFormals, int startLine = 1, + const QVector<CompiledData::CodeOffsetToLine> &lineNumberMapping = QVector<CompiledData::CodeOffsetToLine>()) { + dumpBytecode(bytecode.constData(), bytecode.length(), nLocals, nFormals, startLine, lineNumberMapping); +} union Instr { - enum Type { - FOR_EACH_MOTH_INSTR(MOTH_INSTR_ENUM) - LastInstruction - }; + enum class Type { + FOR_EACH_MOTH_INSTR_ALL(MOTH_INSTR_ENUM) + }; + + static Type wideInstructionType(Type t) { return Type(int(t) | 1); } + static Type narrowInstructionType(Type t) { return Type(int(t) & ~1); } + static bool isWide(Type t) { return int(t) & 1; } + static bool isNarrow(Type t) { return !(int(t) & 1); } + static int encodedLength(Type t) { return int(t) >= 256 ? 2 : 1; } + + static Type unpack(const uchar *c) { if (c[0] == 0x1) return Type(0x100 + c[1]); return Type(c[0]); } + static uchar *pack(uchar *c, Type t) { + if (uint(t) >= 256) { + c[0] = 0x1; + c[1] = uint(t) &0xff; + return c + 2; + } + c[0] = uchar(uint(t)); + return c + 1; + } - struct instr_common { - MOTH_INSTR_HEADER - }; - struct instr_ret { - MOTH_INSTR_HEADER - Param result; - }; + FOR_EACH_MOTH_INSTR_ALL(MOTH_EMIT_STRUCTS) -#ifndef QT_NO_QML_DEBUGGING - struct instr_line { - MOTH_INSTR_HEADER - qint32 lineNumber; - }; - struct instr_debug { - MOTH_INSTR_HEADER - qint32 lineNumber; - }; -#endif // QT_NO_QML_DEBUGGING + FOR_EACH_MOTH_INSTR_ALL(MOTH_EMIT_INSTR_MEMBERS) - struct instr_loadRuntimeString { - MOTH_INSTR_HEADER - int stringId; - Param result; - }; - struct instr_loadRegExp { - MOTH_INSTR_HEADER - int regExpId; - Param result; - }; - struct instr_move { - MOTH_INSTR_HEADER - Param source; - Param result; - }; - struct instr_moveConst { - MOTH_INSTR_HEADER - QV4::ReturnedValue source; - Param result; - }; - struct instr_swapTemps { - MOTH_INSTR_HEADER - Param left; - Param right; - }; - struct instr_loadClosure { - MOTH_INSTR_HEADER - int value; - Param result; - }; - struct instr_loadName { - MOTH_INSTR_HEADER - int name; - Param result; - }; - struct instr_getGlobalLookup { - MOTH_INSTR_HEADER - int index; - Param result; - }; - struct instr_storeName { - MOTH_INSTR_HEADER - int name; - Param source; - }; - struct instr_loadProperty { - MOTH_INSTR_HEADER - int name; - Param base; - Param result; - }; - struct instr_getLookup { - MOTH_INSTR_HEADER - int index; - Param base; - Param result; - }; - struct instr_loadScopeObjectProperty { - MOTH_INSTR_HEADER - 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 - int index; - Param base; - Param result; - }; - struct instr_loadQObjectProperty { - MOTH_INSTR_HEADER - int propertyIndex; - Param base; - Param result; - bool captureRequired; - }; - struct instr_loadAttachedQObjectProperty { - MOTH_INSTR_HEADER - int propertyIndex; - Param result; - int attachedPropertiesId; - }; - struct instr_storeProperty { - MOTH_INSTR_HEADER - int name; - Param base; - Param source; - }; - struct instr_setLookup { - MOTH_INSTR_HEADER - int index; - Param base; - Param source; - }; - struct instr_storeScopeObjectProperty { - MOTH_INSTR_HEADER - Param base; - int propertyIndex; - Param source; - }; - struct instr_storeContextObjectProperty { - MOTH_INSTR_HEADER - Param base; - int propertyIndex; - Param source; - }; - struct instr_storeQObjectProperty { - MOTH_INSTR_HEADER - Param base; - int propertyIndex; - Param source; - }; - struct instr_loadElement { - MOTH_INSTR_HEADER - Param base; - Param index; - Param result; - }; - struct instr_loadElementLookup { - MOTH_INSTR_HEADER - uint lookup; - Param base; - Param index; - Param result; - }; - struct instr_storeElement { - MOTH_INSTR_HEADER - Param base; - Param index; - Param source; - }; - struct instr_storeElementLookup { - MOTH_INSTR_HEADER - uint lookup; - Param base; - Param index; - Param source; - }; - struct instr_push { - MOTH_INSTR_HEADER - quint32 value; - }; - struct instr_callValue { - MOTH_INSTR_HEADER - quint32 argc; - quint32 callData; - Param dest; - Param result; - }; - struct instr_callProperty { - MOTH_INSTR_HEADER - int name; - quint32 argc; - quint32 callData; - Param base; - Param result; - }; - struct instr_callPropertyLookup { - MOTH_INSTR_HEADER - int lookupIndex; - quint32 argc; - quint32 callData; - Param base; - Param result; - }; - struct instr_callScopeObjectProperty { - MOTH_INSTR_HEADER - int index; - quint32 argc; - quint32 callData; - Param base; - Param result; - }; - struct instr_callContextObjectProperty { - MOTH_INSTR_HEADER - int index; - quint32 argc; - quint32 callData; - Param base; - Param result; - }; - struct instr_callElement { - MOTH_INSTR_HEADER - Param base; - Param index; - quint32 argc; - quint32 callData; - Param result; - }; - struct instr_callActivationProperty { - MOTH_INSTR_HEADER - int name; - quint32 argc; - quint32 callData; - Param result; - }; - struct instr_callGlobalLookup { - MOTH_INSTR_HEADER - int index; - quint32 argc; - quint32 callData; - Param result; - }; - struct instr_setExceptionHandler { - MOTH_INSTR_HEADER - qptrdiff offset; - }; - struct instr_callBuiltinThrow { - MOTH_INSTR_HEADER - Param arg; - }; - struct instr_callBuiltinUnwindException { - MOTH_INSTR_HEADER - Param result; - }; - struct instr_callBuiltinPushCatchScope { - MOTH_INSTR_HEADER - int name; - }; - struct instr_callBuiltinPushScope { - MOTH_INSTR_HEADER - Param arg; - }; - struct instr_callBuiltinPopScope { - MOTH_INSTR_HEADER - }; - struct instr_callBuiltinForeachIteratorObject { - MOTH_INSTR_HEADER - Param arg; - Param result; - }; - struct instr_callBuiltinForeachNextPropertyName { - MOTH_INSTR_HEADER - Param arg; - Param result; - }; - struct instr_callBuiltinDeleteMember { - MOTH_INSTR_HEADER - int member; - Param base; - Param result; - }; - struct instr_callBuiltinDeleteSubscript { - MOTH_INSTR_HEADER - Param base; - Param index; - Param result; - }; - struct instr_callBuiltinDeleteName { - MOTH_INSTR_HEADER - int name; - Param result; - }; - struct instr_callBuiltinTypeofScopeObjectProperty { - MOTH_INSTR_HEADER - int index; - Param base; - Param result; - }; - struct instr_callBuiltinTypeofContextObjectProperty { - MOTH_INSTR_HEADER - int index; - Param base; - Param result; - }; - struct instr_callBuiltinTypeofMember { - MOTH_INSTR_HEADER - int member; - Param base; - Param result; - }; - struct instr_callBuiltinTypeofSubscript { - MOTH_INSTR_HEADER - Param base; - Param index; - Param result; - }; - struct instr_callBuiltinTypeofName { - MOTH_INSTR_HEADER - int name; - Param result; - }; - struct instr_callBuiltinTypeofValue { - MOTH_INSTR_HEADER - Param value; - Param result; - }; - struct instr_callBuiltinDeclareVar { - MOTH_INSTR_HEADER - int varName; - bool isDeletable; - }; - struct instr_callBuiltinDefineArray { - MOTH_INSTR_HEADER - quint32 argc; - quint32 args; - Param result; - }; - struct instr_callBuiltinDefineObjectLiteral { - MOTH_INSTR_HEADER - int internalClassId; - uint arrayValueCount; - uint arrayGetterSetterCountAndFlags; // 30 bits for count, 1 bit for needsSparseArray boolean - quint32 args; - Param result; - }; - struct instr_callBuiltinSetupArgumentsObject { - MOTH_INSTR_HEADER - Param result; - }; - struct instr_callBuiltinConvertThisToObject { - MOTH_INSTR_HEADER - }; - struct instr_createValue { - MOTH_INSTR_HEADER - quint32 argc; - quint32 callData; - Param func; - Param result; - }; - struct instr_createProperty { - MOTH_INSTR_HEADER - int name; - quint32 argc; - quint32 callData; - Param base; - Param result; - }; - struct instr_constructPropertyLookup { - MOTH_INSTR_HEADER - int index; - quint32 argc; - quint32 callData; - Param base; - Param result; - }; - struct instr_createActivationProperty { - MOTH_INSTR_HEADER - int name; - quint32 argc; - quint32 callData; - Param result; - }; - struct instr_constructGlobalLookup { - MOTH_INSTR_HEADER - int index; - quint32 argc; - quint32 callData; - Param result; - }; - struct instr_jump { - MOTH_INSTR_HEADER - ptrdiff_t offset; - }; - struct instr_jumpEq { - MOTH_INSTR_HEADER - ptrdiff_t offset; - Param condition; - }; - struct instr_jumpNe { - MOTH_INSTR_HEADER - ptrdiff_t offset; - Param condition; - }; - struct instr_unot { - MOTH_INSTR_HEADER - Param source; - Param result; - }; - struct instr_unotBool { - MOTH_INSTR_HEADER - Param source; - Param result; - }; - struct instr_uplus { - MOTH_INSTR_HEADER - Param source; - Param result; - }; - struct instr_uminus { - MOTH_INSTR_HEADER - Param source; - Param result; - }; - struct instr_ucompl { - MOTH_INSTR_HEADER - Param source; - Param result; - }; - struct instr_ucomplInt { - MOTH_INSTR_HEADER - Param source; - Param result; - }; - struct instr_increment { - MOTH_INSTR_HEADER - Param source; - Param result; - }; - struct instr_decrement { - MOTH_INSTR_HEADER - Param source; - Param result; - }; - struct instr_binop { - MOTH_INSTR_HEADER - int alu; // QV4::Runtime::RuntimeMethods enum value - Param lhs; - Param rhs; - Param result; - }; - struct instr_add { - MOTH_INSTR_HEADER - Param lhs; - Param rhs; - Param result; - }; - struct instr_bitAnd { - MOTH_INSTR_HEADER - Param lhs; - Param rhs; - Param result; - }; - struct instr_bitOr { - MOTH_INSTR_HEADER - Param lhs; - Param rhs; - Param result; - }; - struct instr_bitXor { - MOTH_INSTR_HEADER - Param lhs; - Param rhs; - Param result; - }; - struct instr_shr { - MOTH_INSTR_HEADER - Param lhs; - Param rhs; - Param result; - }; - struct instr_shl { - MOTH_INSTR_HEADER - Param lhs; - Param rhs; - Param result; - }; - struct instr_bitAndConst { - MOTH_INSTR_HEADER - Param lhs; - int rhs; - Param result; - }; - struct instr_bitOrConst { - MOTH_INSTR_HEADER - Param lhs; - int rhs; - Param result; - }; - struct instr_bitXorConst { - MOTH_INSTR_HEADER - Param lhs; - int rhs; - Param result; - }; - struct instr_shrConst { - MOTH_INSTR_HEADER - Param lhs; - int rhs; - Param result; - }; - struct instr_shlConst { - MOTH_INSTR_HEADER - Param lhs; - int rhs; - Param result; - }; - struct instr_mul { - MOTH_INSTR_HEADER - Param lhs; - Param rhs; - Param result; - }; - struct instr_sub { - MOTH_INSTR_HEADER - Param lhs; - Param rhs; - Param result; - }; - struct instr_binopContext { - MOTH_INSTR_HEADER - uint alu; // offset inside the runtime methods - Param lhs; - Param rhs; - Param result; - }; - struct instr_loadThis { - MOTH_INSTR_HEADER - Param result; - }; - struct instr_loadQmlContext { - MOTH_INSTR_HEADER - Param result; - }; - struct instr_loadQmlImportedScripts { - MOTH_INSTR_HEADER - Param result; - }; - struct instr_loadQmlSingleton { - MOTH_INSTR_HEADER - Param result; - int name; - }; + int argumentsAsInts[4]; +}; - instr_common common; - instr_ret ret; - instr_line line; - instr_debug debug; - instr_loadRuntimeString loadRuntimeString; - instr_loadRegExp loadRegExp; - instr_move move; - instr_moveConst moveConst; - instr_swapTemps swapTemps; - instr_loadClosure loadClosure; - instr_loadName loadName; - instr_getGlobalLookup getGlobalLookup; - instr_storeName storeName; - instr_loadElement loadElement; - instr_loadElementLookup loadElementLookup; - instr_storeElement storeElement; - instr_storeElementLookup storeElementLookup; - instr_loadProperty loadProperty; - instr_getLookup getLookup; - instr_loadScopeObjectProperty loadScopeObjectProperty; - instr_loadContextObjectProperty loadContextObjectProperty; - instr_loadIdObject loadIdObject; - instr_loadQObjectProperty loadQObjectProperty; - instr_loadAttachedQObjectProperty loadAttachedQObjectProperty; - instr_storeProperty storeProperty; - instr_setLookup setLookup; - instr_storeScopeObjectProperty storeScopeObjectProperty; - instr_storeContextObjectProperty storeContextObjectProperty; - instr_storeQObjectProperty storeQObjectProperty; - instr_push push; - instr_callValue callValue; - instr_callProperty callProperty; - instr_callPropertyLookup callPropertyLookup; - instr_callScopeObjectProperty callScopeObjectProperty; - instr_callContextObjectProperty callContextObjectProperty; - instr_callElement callElement; - instr_callActivationProperty callActivationProperty; - instr_callGlobalLookup callGlobalLookup; - instr_callBuiltinThrow callBuiltinThrow; - instr_setExceptionHandler setExceptionHandler; - instr_callBuiltinUnwindException callBuiltinUnwindException; - instr_callBuiltinPushCatchScope callBuiltinPushCatchScope; - instr_callBuiltinPushScope callBuiltinPushScope; - instr_callBuiltinPopScope callBuiltinPopScope; - instr_callBuiltinForeachIteratorObject callBuiltinForeachIteratorObject; - instr_callBuiltinForeachNextPropertyName callBuiltinForeachNextPropertyName; - instr_callBuiltinDeleteMember callBuiltinDeleteMember; - instr_callBuiltinDeleteSubscript callBuiltinDeleteSubscript; - instr_callBuiltinDeleteName callBuiltinDeleteName; - instr_callBuiltinTypeofScopeObjectProperty callBuiltinTypeofScopeObjectProperty; - instr_callBuiltinTypeofContextObjectProperty callBuiltinTypeofContextObjectProperty; - instr_callBuiltinTypeofMember callBuiltinTypeofMember; - instr_callBuiltinTypeofSubscript callBuiltinTypeofSubscript; - instr_callBuiltinTypeofName callBuiltinTypeofName; - instr_callBuiltinTypeofValue callBuiltinTypeofValue; - instr_callBuiltinDeclareVar callBuiltinDeclareVar; - instr_callBuiltinDefineArray callBuiltinDefineArray; - instr_callBuiltinDefineObjectLiteral callBuiltinDefineObjectLiteral; - instr_callBuiltinSetupArgumentsObject callBuiltinSetupArgumentsObject; - instr_callBuiltinConvertThisToObject callBuiltinConvertThisToObject; - instr_createValue createValue; - instr_createProperty createProperty; - instr_constructPropertyLookup constructPropertyLookup; - instr_createActivationProperty createActivationProperty; - instr_constructGlobalLookup constructGlobalLookup; - instr_jump jump; - instr_jumpEq jumpEq; - instr_jumpNe jumpNe; - instr_unot unot; - instr_unotBool unotBool; - instr_uplus uplus; - instr_uminus uminus; - instr_ucompl ucompl; - instr_ucomplInt ucomplInt; - instr_increment increment; - instr_decrement decrement; - instr_binop binop; - instr_add add; - instr_bitAnd bitAnd; - instr_bitOr bitOr; - instr_bitXor bitXor; - instr_shr shr; - instr_shl shl; - instr_bitAndConst bitAndConst; - instr_bitOrConst bitOrConst; - instr_bitXorConst bitXorConst; - instr_shrConst shrConst; - instr_shlConst shlConst; - instr_mul mul; - instr_sub sub; - instr_binopContext binopContext; - instr_loadThis loadThis; - instr_loadQmlContext loadQmlContext; - instr_loadQmlImportedScripts loadQmlImportedScripts; - instr_loadQmlSingleton loadQmlSingleton; - - static int size(Type type); +struct InstrInfo +{ + static const int argumentCount[]; + static int size(Instr::Type type); }; template<int N> struct InstrMeta { }; -#define MOTH_INSTR_META_TEMPLATE(I, FMT) \ - template<> struct InstrMeta<(int)Instr::I> { \ - enum { Size = MOTH_INSTR_SIZE(I, FMT) }; \ - typedef Instr::instr_##FMT DataType; \ - static const DataType &data(const Instr &instr) { return instr.FMT; } \ - static void setData(Instr &instr, const DataType &v) { instr.FMT = v; } \ - static void setDataNoCommon(Instr &instr, const DataType &v) \ - { memcpy(reinterpret_cast<char *>(&instr.FMT) + sizeof(Instr::instr_common), \ - reinterpret_cast<const char *>(&v) + sizeof(Instr::instr_common), \ - Size - sizeof(Instr::instr_common)); } \ - }; -FOR_EACH_MOTH_INSTR(MOTH_INSTR_META_TEMPLATE); +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wuninitialized") +QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") +#define MOTH_INSTR_META_TEMPLATE(I) \ + template<> struct InstrMeta<int(Instr::Type::I)> { \ + enum { Size = MOTH_INSTR_SIZE(I) }; \ + typedef Instr::instr_##I DataType; \ + static const DataType &data(const Instr &instr) { return instr.I; } \ + static void setData(Instr &instr, const DataType &v) \ + { memcpy(reinterpret_cast<char *>(&instr.I), \ + reinterpret_cast<const char *>(&v), \ + Size); } \ + }; +FOR_EACH_MOTH_INSTR_ALL(MOTH_INSTR_META_TEMPLATE); #undef MOTH_INSTR_META_TEMPLATE +QT_WARNING_POP template<int InstrType> class InstrData : public InstrMeta<InstrType>::DataType { }; +struct Instruction { +#define MOTH_INSTR_DATA_TYPEDEF(I) typedef InstrData<int(Instr::Type::I)> I; +FOR_EACH_MOTH_INSTR_ALL(MOTH_INSTR_DATA_TYPEDEF) +#undef MOTH_INSTR_DATA_TYPEDEF +private: + Instruction(); +}; + } // namespace Moth } // namespace QV4 diff --git a/src/qml/compiler/qv4isel_moth.cpp b/src/qml/compiler/qv4isel_moth.cpp deleted file mode 100644 index fb805dce02..0000000000 --- a/src/qml/compiler/qv4isel_moth.cpp +++ /dev/null @@ -1,1528 +0,0 @@ -/**************************************************************************** -** -** 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 "qv4isel_util_p.h" -#include "qv4isel_moth_p.h" -#include "qv4ssa_p.h" -#include <private/qv4compileddata_p.h> -#include <wtf/MathExtras.h> - -#if !defined(V4_BOOTSTRAP) -#include "qv4vme_moth_p.h" -#include <private/qv4function_p.h> -#endif - -#undef USE_TYPE_INFO - -using namespace QV4; -using namespace QV4::Moth; - -namespace { - -inline QV4::Runtime::RuntimeMethods aluOpFunction(IR::AluOp op) -{ - switch (op) { - case IR::OpInvalid: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpIfTrue: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpNot: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpUMinus: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpUPlus: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpCompl: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpBitAnd: - return QV4::Runtime::bitAnd; - case IR::OpBitOr: - return QV4::Runtime::bitOr; - case IR::OpBitXor: - return QV4::Runtime::bitXor; - case IR::OpAdd: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpSub: - return QV4::Runtime::sub; - case IR::OpMul: - return QV4::Runtime::mul; - case IR::OpDiv: - return QV4::Runtime::div; - case IR::OpMod: - return QV4::Runtime::mod; - case IR::OpLShift: - return QV4::Runtime::shl; - case IR::OpRShift: - return QV4::Runtime::shr; - case IR::OpURShift: - return QV4::Runtime::ushr; - case IR::OpGt: - return QV4::Runtime::greaterThan; - case IR::OpLt: - return QV4::Runtime::lessThan; - case IR::OpGe: - return QV4::Runtime::greaterEqual; - case IR::OpLe: - return QV4::Runtime::lessEqual; - case IR::OpEqual: - return QV4::Runtime::equal; - case IR::OpNotEqual: - return QV4::Runtime::notEqual; - case IR::OpStrictEqual: - return QV4::Runtime::strictEqual; - case IR::OpStrictNotEqual: - return QV4::Runtime::strictNotEqual; - case IR::OpInstanceof: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpIn: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpAnd: - return QV4::Runtime::InvalidRuntimeMethod; - case IR::OpOr: - return QV4::Runtime::InvalidRuntimeMethod; - default: - Q_ASSERT(!"Unknown AluOp"); - return QV4::Runtime::InvalidRuntimeMethod; - } -}; - -inline bool isNumberType(IR::Expr *e) -{ - switch (e->type) { - case IR::SInt32Type: - case IR::UInt32Type: - case IR::DoubleType: - return true; - default: - return false; - } -} - -inline bool isIntegerType(IR::Expr *e) -{ - switch (e->type) { - case IR::SInt32Type: - case IR::UInt32Type: - return true; - default: - return false; - } -} - -inline bool isBoolType(IR::Expr *e) -{ - return (e->type == IR::BoolType); -} - -} // anonymous namespace - -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) - , _codeNext(0) - , _codeEnd(0) - , _currentStatement(0) - , compilationUnit(new CompilationUnit) -{ - setUseTypeInference(false); -} - -InstructionSelection::~InstructionSelection() -{ -} - -void InstructionSelection::run(int functionIndex) -{ - IR::Function *function = irModule->functions[functionIndex]; - IR::BasicBlock *block = 0, *nextBlock = 0; - - QHash<IR::BasicBlock *, QVector<ptrdiff_t> > patches; - QHash<IR::BasicBlock *, ptrdiff_t> addrs; - - int codeSize = 4096; - uchar *codeStart = new uchar[codeSize]; - memset(codeStart, 0, codeSize); - uchar *codeNext = codeStart; - uchar *codeEnd = codeStart + codeSize; - - qSwap(_function, function); - qSwap(block, _block); - qSwap(nextBlock, _nextBlock); - qSwap(patches, _patches); - qSwap(addrs, _addrs); - qSwap(codeStart, _codeStart); - qSwap(codeNext, _codeNext); - qSwap(codeEnd, _codeEnd); - - IR::Optimizer opt(_function); - opt.run(qmlEngine, useTypeInference, /*peelLoops =*/ false); - if (opt.isInSSA()) { - static const bool doStackSlotAllocation = - qEnvironmentVariableIsEmpty("QV4_NO_INTERPRETER_STACK_SLOT_ALLOCATION"); - - if (doStackSlotAllocation) { - IR::AllocateStackSlots(opt.lifeTimeIntervals()).forFunction(_function); - } else { - opt.convertOutOfSSA(); - ConvertTemps().toStackSlots(_function); - } - opt.showMeTheCode(_function, "After stack slot allocation"); - } else { - ConvertTemps().toStackSlots(_function); - } - - BitVector removableJumps = opt.calculateOptionalJumps(); - qSwap(_removableJumps, removableJumps); - - IR::Stmt *cs = 0; - qSwap(_currentStatement, cs); - - int locals = frameSize(); - Q_ASSERT(locals >= 0); - - IR::BasicBlock *exceptionHandler = 0; - - Instruction::Push push; - push.value = quint32(locals); - addInstruction(push); - - currentLine = 0; - const QVector<IR::BasicBlock *> &basicBlocks = _function->basicBlocks(); - for (int i = 0, ei = basicBlocks.size(); i != ei; ++i) { - blockNeedsDebugInstruction = irModule->debugMode; - _block = basicBlocks[i]; - _nextBlock = (i < ei - 1) ? basicBlocks[i + 1] : 0; - _addrs.insert(_block, _codeNext - _codeStart); - - if (_block->catchBlock != exceptionHandler) { - Instruction::SetExceptionHandler set; - set.offset = 0; - if (_block->catchBlock) { - ptrdiff_t loc = addInstruction(set) + (((const char *)&set.offset) - ((const char *)&set)); - _patches[_block->catchBlock].append(loc); - } else { - addInstruction(set); - } - exceptionHandler = _block->catchBlock; - } else if (_block->catchBlock == nullptr && _block->index() != 0 && _block->in.isEmpty()) { - exceptionHandler = nullptr; - Instruction::SetExceptionHandler set; - set.offset = 0; - addInstruction(set); - } - - for (IR::Stmt *s : _block->statements()) { - _currentStatement = s; - - if (s->location.isValid()) { - 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; - addInstruction(debug); - } else { - Instruction::Line line; - line.lineNumber = currentLine; - addInstruction(line); - } -#endif - } - } - - visit(s); - } - } - - // TODO: patch stack size (the push instruction) - patchJumpAddresses(); - - codeRefs.insert(_function, squeezeCode()); - - qSwap(_currentStatement, cs); - qSwap(_removableJumps, removableJumps); - qSwap(_function, function); - qSwap(block, _block); - qSwap(nextBlock, _nextBlock); - qSwap(patches, _patches); - qSwap(addrs, _addrs); - qSwap(codeStart, _codeStart); - qSwap(codeNext, _codeNext); - qSwap(codeEnd, _codeEnd); - - delete[] codeStart; -} - -QQmlRefPointer<QV4::CompiledData::CompilationUnit> InstructionSelection::backendCompileStep() -{ - compilationUnit->codeRefs.resize(irModule->functions.size()); - int i = 0; - for (IR::Function *irFunction : qAsConst(irModule->functions)) - compilationUnit->codeRefs[i++] = codeRefs[irFunction]; - QQmlRefPointer<QV4::CompiledData::CompilationUnit> result; - result.adopt(compilationUnit.take()); - return result; -} - -void InstructionSelection::callValue(IR::Expr *value, IR::ExprList *args, IR::Expr *result) -{ - Instruction::CallValue call; - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.dest = getParam(value); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callQmlContextProperty(IR::Expr *base, IR::Member::MemberKind kind, int propertyIndex, IR::ExprList *args, IR::Expr *result) -{ - if (kind == IR::Member::MemberOfQmlScopeObject) { - Instruction::CallScopeObjectProperty call; - call.base = getParam(base); - call.index = propertyIndex; - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.result = getResultParam(result); - addInstruction(call); - } else if (kind == IR::Member::MemberOfQmlContextObject) { - Instruction::CallContextObjectProperty call; - call.base = getParam(base); - call.index = propertyIndex; - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.result = getResultParam(result); - addInstruction(call); - } else { - Q_ASSERT(false); - } -} - -void InstructionSelection::callProperty(IR::Expr *base, const QString &name, IR::ExprList *args, - IR::Expr *result) -{ - if (useFastLookups) { - Instruction::CallPropertyLookup call; - call.base = getParam(base); - call.lookupIndex = registerGetterLookup(name); - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.result = getResultParam(result); - addInstruction(call); - } else { - // call the property on the loaded base - Instruction::CallProperty call; - call.base = getParam(base); - call.name = registerString(name); - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.result = getResultParam(result); - addInstruction(call); - } -} - -void InstructionSelection::callSubscript(IR::Expr *base, IR::Expr *index, IR::ExprList *args, - IR::Expr *result) -{ - // call the property on the loaded base - Instruction::CallElement call; - call.base = getParam(base); - call.index = getParam(index); - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::convertType(IR::Expr *source, IR::Expr *target) -{ - // FIXME: do something more useful with this info - if (target->type & IR::NumberType && !(source->type & IR::NumberType)) - unop(IR::OpUPlus, source, target); - else - copyValue(source, target); -} - -void InstructionSelection::constructActivationProperty(IR::Name *func, - IR::ExprList *args, - IR::Expr *target) -{ - if (useFastLookups && func->global) { - Instruction::ConstructGlobalLookup call; - call.index = registerGlobalGetterLookup(*func->id); - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.result = getResultParam(target); - addInstruction(call); - return; - } - Instruction::CreateActivationProperty create; - create.name = registerString(*func->id); - prepareCallArgs(args, create.argc); - create.callData = callDataStart(); - create.result = getResultParam(target); - addInstruction(create); -} - -void InstructionSelection::constructProperty(IR::Expr *base, const QString &name, IR::ExprList *args, IR::Expr *target) -{ - if (useFastLookups) { - Instruction::ConstructPropertyLookup call; - call.base = getParam(base); - call.index = registerGetterLookup(name); - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.result = getResultParam(target); - addInstruction(call); - return; - } - Instruction::CreateProperty create; - create.base = getParam(base); - create.name = registerString(name); - prepareCallArgs(args, create.argc); - create.callData = callDataStart(); - create.result = getResultParam(target); - addInstruction(create); -} - -void InstructionSelection::constructValue(IR::Expr *value, IR::ExprList *args, IR::Expr *target) -{ - Instruction::CreateValue create; - create.func = getParam(value); - prepareCallArgs(args, create.argc); - create.callData = callDataStart(); - create.result = getResultParam(target); - addInstruction(create); -} - -void InstructionSelection::loadThisObject(IR::Expr *e) -{ - Instruction::LoadThis load; - load.result = getResultParam(e); - addInstruction(load); -} - -void InstructionSelection::loadQmlContext(IR::Expr *e) -{ - Instruction::LoadQmlContext load; - load.result = getResultParam(e); - addInstruction(load); -} - -void InstructionSelection::loadQmlImportedScripts(IR::Expr *e) -{ - Instruction::LoadQmlImportedScripts load; - load.result = getResultParam(e); - addInstruction(load); -} - -void InstructionSelection::loadQmlSingleton(const QString &name, IR::Expr *e) -{ - Instruction::LoadQmlSingleton load; - load.result = getResultParam(e); - load.name = registerString(name); - addInstruction(load); -} - -void InstructionSelection::loadConst(IR::Const *sourceConst, IR::Expr *e) -{ - Q_ASSERT(sourceConst); - - Instruction::MoveConst move; - move.source = convertToValue(sourceConst).asReturnedValue(); - move.result = getResultParam(e); - addInstruction(move); -} - -void InstructionSelection::loadString(const QString &str, IR::Expr *target) -{ - Instruction::LoadRuntimeString load; - load.stringId = registerString(str); - load.result = getResultParam(target); - addInstruction(load); -} - -void InstructionSelection::loadRegexp(IR::RegExp *sourceRegexp, IR::Expr *target) -{ - Instruction::LoadRegExp load; - load.regExpId = registerRegExp(sourceRegexp); - load.result = getResultParam(target); - addInstruction(load); -} - -void InstructionSelection::getActivationProperty(const IR::Name *name, IR::Expr *target) -{ - if (useFastLookups && name->global) { - Instruction::GetGlobalLookup load; - load.index = registerGlobalGetterLookup(*name->id); - load.result = getResultParam(target); - addInstruction(load); - return; - } - Instruction::LoadName load; - load.name = registerString(*name->id); - load.result = getResultParam(target); - addInstruction(load); -} - -void InstructionSelection::setActivationProperty(IR::Expr *source, const QString &targetName) -{ - Instruction::StoreName store; - store.source = getParam(source); - store.name = registerString(targetName); - addInstruction(store); -} - -void InstructionSelection::initClosure(IR::Closure *closure, IR::Expr *target) -{ - int id = closure->value; - Instruction::LoadClosure load; - load.value = id; - load.result = getResultParam(target); - addInstruction(load); -} - -void InstructionSelection::getProperty(IR::Expr *base, const QString &name, IR::Expr *target) -{ - if (useFastLookups) { - Instruction::GetLookup load; - load.base = getParam(base); - load.index = registerGetterLookup(name); - load.result = getResultParam(target); - addInstruction(load); - return; - } - Instruction::LoadProperty load; - load.base = getParam(base); - load.name = registerString(name); - load.result = getResultParam(target); - addInstruction(load); -} - -void InstructionSelection::setProperty(IR::Expr *source, IR::Expr *targetBase, - const QString &targetName) -{ - if (useFastLookups) { - Instruction::SetLookup store; - store.base = getParam(targetBase); - store.index = registerSetterLookup(targetName); - store.source = getParam(source); - addInstruction(store); - return; - } - Instruction::StoreProperty store; - store.base = getParam(targetBase); - store.name = registerString(targetName); - store.source = getParam(source); - addInstruction(store); -} - -void InstructionSelection::setQmlContextProperty(IR::Expr *source, IR::Expr *targetBase, IR::Member::MemberKind kind, int propertyIndex) -{ - if (kind == IR::Member::MemberOfQmlScopeObject) { - Instruction::StoreScopeObjectProperty store; - store.base = getParam(targetBase); - store.propertyIndex = propertyIndex; - store.source = getParam(source); - addInstruction(store); - } else if (kind == IR::Member::MemberOfQmlContextObject) { - Instruction::StoreContextObjectProperty store; - store.base = getParam(targetBase); - store.propertyIndex = propertyIndex; - store.source = getParam(source); - addInstruction(store); - } else { - Q_ASSERT(false); - } -} - -void InstructionSelection::setQObjectProperty(IR::Expr *source, IR::Expr *targetBase, int propertyIndex) -{ - Instruction::StoreQObjectProperty store; - store.base = getParam(targetBase); - store.propertyIndex = propertyIndex; - store.source = getParam(source); - addInstruction(store); -} - -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) { - Instruction::LoadIdObject load; - load.base = getParam(source); - load.index = index; - load.result = getResultParam(target); - addInstruction(load); - } else { - Q_ASSERT(false); - } -} - -void InstructionSelection::getQObjectProperty(IR::Expr *base, int propertyIndex, bool captureRequired, bool isSingletonProperty, int attachedPropertiesId, IR::Expr *target) -{ - if (attachedPropertiesId != 0) { - Instruction::LoadAttachedQObjectProperty load; - load.propertyIndex = propertyIndex; - load.result = getResultParam(target); - load.attachedPropertiesId = attachedPropertiesId; - addInstruction(load); - } else if (isSingletonProperty) { - Instruction::LoadSingletonQObjectProperty load; - load.base = getParam(base); - load.propertyIndex = propertyIndex; - load.result = getResultParam(target); - load.captureRequired = captureRequired; - addInstruction(load); - } else { - Instruction::LoadQObjectProperty load; - load.base = getParam(base); - load.propertyIndex = propertyIndex; - load.result = getResultParam(target); - load.captureRequired = captureRequired; - addInstruction(load); - } -} - -void InstructionSelection::getElement(IR::Expr *base, IR::Expr *index, IR::Expr *target) -{ - if (0 && useFastLookups) { - Instruction::LoadElementLookup load; - load.lookup = registerIndexedGetterLookup(); - load.base = getParam(base); - load.index = getParam(index); - load.result = getResultParam(target); - addInstruction(load); - return; - } - Instruction::LoadElement load; - load.base = getParam(base); - load.index = getParam(index); - load.result = getResultParam(target); - addInstruction(load); -} - -void InstructionSelection::setElement(IR::Expr *source, IR::Expr *targetBase, - IR::Expr *targetIndex) -{ - if (0 && useFastLookups) { - Instruction::StoreElementLookup store; - store.lookup = registerIndexedSetterLookup(); - store.base = getParam(targetBase); - store.index = getParam(targetIndex); - store.source = getParam(source); - addInstruction(store); - return; - } - Instruction::StoreElement store; - store.base = getParam(targetBase); - store.index = getParam(targetIndex); - store.source = getParam(source); - addInstruction(store); -} - -void InstructionSelection::copyValue(IR::Expr *source, IR::Expr *target) -{ - Instruction::Move move; - move.source = getParam(source); - move.result = getResultParam(target); - if (move.source != move.result) - addInstruction(move); -} - -void InstructionSelection::swapValues(IR::Expr *source, IR::Expr *target) -{ - Instruction::SwapTemps swap; - swap.left = getParam(source); - swap.right = getParam(target); - addInstruction(swap); -} - -void InstructionSelection::unop(IR::AluOp oper, IR::Expr *source, IR::Expr *target) -{ - switch (oper) { - case IR::OpIfTrue: - Q_ASSERT(!"unreachable"); break; - case IR::OpNot: { - // ### enabling this fails in some cases, where apparently the value is not a bool at runtime - if (0 && isBoolType(source)) { - Instruction::UNotBool unot; - unot.source = getParam(source); - unot.result = getResultParam(target); - addInstruction(unot); - return; - } - Instruction::UNot unot; - unot.source = getParam(source); - unot.result = getResultParam(target); - addInstruction(unot); - return; - } - case IR::OpUMinus: { - Instruction::UMinus uminus; - uminus.source = getParam(source); - uminus.result = getResultParam(target); - addInstruction(uminus); - return; - } - case IR::OpUPlus: { - if (isNumberType(source)) { - // use a move - Instruction::Move move; - move.source = getParam(source); - move.result = getResultParam(target); - if (move.source != move.result) - addInstruction(move); - return; - } - Instruction::UPlus uplus; - uplus.source = getParam(source); - uplus.result = getResultParam(target); - addInstruction(uplus); - return; - } - case IR::OpCompl: { - // ### enabling this fails in some cases, where apparently the value is not a int at runtime - if (0 && isIntegerType(source)) { - Instruction::UComplInt unot; - unot.source = getParam(source); - unot.result = getResultParam(target); - addInstruction(unot); - return; - } - Instruction::UCompl ucompl; - ucompl.source = getParam(source); - ucompl.result = getResultParam(target); - addInstruction(ucompl); - return; - } - case IR::OpIncrement: { - Instruction::Increment inc; - inc.source = getParam(source); - inc.result = getResultParam(target); - addInstruction(inc); - return; - } - case IR::OpDecrement: { - Instruction::Decrement dec; - dec.source = getParam(source); - dec.result = getResultParam(target); - addInstruction(dec); - return; - } - default: break; - } // switch - - Q_ASSERT(!"unreachable"); -} - -void InstructionSelection::binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Expr *target) -{ - binopHelper(oper, leftSource, rightSource, target); -} - -Param InstructionSelection::binopHelper(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Expr *target) -{ - if (oper == IR::OpAdd) { - Instruction::Add add; - add.lhs = getParam(leftSource); - add.rhs = getParam(rightSource); - add.result = getResultParam(target); - addInstruction(add); - return add.result; - } - if (oper == IR::OpSub) { - Instruction::Sub sub; - sub.lhs = getParam(leftSource); - sub.rhs = getParam(rightSource); - sub.result = getResultParam(target); - addInstruction(sub); - return sub.result; - } - if (oper == IR::OpMul) { - Instruction::Mul mul; - mul.lhs = getParam(leftSource); - mul.rhs = getParam(rightSource); - mul.result = getResultParam(target); - addInstruction(mul); - return mul.result; - } - if (oper == IR::OpBitAnd) { - if (leftSource->asConst()) - qSwap(leftSource, rightSource); - if (IR::Const *c = rightSource->asConst()) { - Instruction::BitAndConst bitAnd; - bitAnd.lhs = getParam(leftSource); - bitAnd.rhs = convertToValue(c).Value::toInt32(); - bitAnd.result = getResultParam(target); - addInstruction(bitAnd); - return bitAnd.result; - } - Instruction::BitAnd bitAnd; - bitAnd.lhs = getParam(leftSource); - bitAnd.rhs = getParam(rightSource); - bitAnd.result = getResultParam(target); - addInstruction(bitAnd); - return bitAnd.result; - } - if (oper == IR::OpBitOr) { - if (leftSource->asConst()) - qSwap(leftSource, rightSource); - if (IR::Const *c = rightSource->asConst()) { - Instruction::BitOrConst bitOr; - bitOr.lhs = getParam(leftSource); - bitOr.rhs = convertToValue(c).Value::toInt32(); - bitOr.result = getResultParam(target); - addInstruction(bitOr); - return bitOr.result; - } - Instruction::BitOr bitOr; - bitOr.lhs = getParam(leftSource); - bitOr.rhs = getParam(rightSource); - bitOr.result = getResultParam(target); - addInstruction(bitOr); - return bitOr.result; - } - if (oper == IR::OpBitXor) { - if (leftSource->asConst()) - qSwap(leftSource, rightSource); - if (IR::Const *c = rightSource->asConst()) { - Instruction::BitXorConst bitXor; - bitXor.lhs = getParam(leftSource); - bitXor.rhs = convertToValue(c).Value::toInt32(); - bitXor.result = getResultParam(target); - addInstruction(bitXor); - return bitXor.result; - } - Instruction::BitXor bitXor; - bitXor.lhs = getParam(leftSource); - bitXor.rhs = getParam(rightSource); - bitXor.result = getResultParam(target); - addInstruction(bitXor); - return bitXor.result; - } - if (oper == IR::OpRShift) { - if (IR::Const *c = rightSource->asConst()) { - Instruction::ShrConst shr; - shr.lhs = getParam(leftSource); - shr.rhs = convertToValue(c).Value::toInt32() & 0x1f; - shr.result = getResultParam(target); - addInstruction(shr); - return shr.result; - } - Instruction::Shr shr; - shr.lhs = getParam(leftSource); - shr.rhs = getParam(rightSource); - shr.result = getResultParam(target); - addInstruction(shr); - return shr.result; - } - if (oper == IR::OpLShift) { - if (IR::Const *c = rightSource->asConst()) { - Instruction::ShlConst shl; - shl.lhs = getParam(leftSource); - shl.rhs = convertToValue(c).Value::toInt32() & 0x1f; - shl.result = getResultParam(target); - addInstruction(shl); - return shl.result; - } - Instruction::Shl shl; - shl.lhs = getParam(leftSource); - shl.rhs = getParam(rightSource); - shl.result = getResultParam(target); - addInstruction(shl); - return shl.result; - } - - if (oper == IR::OpInstanceof || oper == IR::OpIn || oper == IR::OpAdd) { - Instruction::BinopContext binop; - if (oper == IR::OpInstanceof) - binop.alu = QV4::Runtime::instanceof; - else if (oper == IR::OpIn) - binop.alu = QV4::Runtime::in; - else - binop.alu = QV4::Runtime::add; - binop.lhs = getParam(leftSource); - binop.rhs = getParam(rightSource); - binop.result = getResultParam(target); - Q_ASSERT(binop.alu != QV4::Runtime::InvalidRuntimeMethod); - addInstruction(binop); - return binop.result; - } else { - auto binopFunc = aluOpFunction(oper); - Q_ASSERT(binopFunc != QV4::Runtime::InvalidRuntimeMethod); - Instruction::Binop binop; - binop.alu = binopFunc; - binop.lhs = getParam(leftSource); - binop.rhs = getParam(rightSource); - binop.result = getResultParam(target); - addInstruction(binop); - return binop.result; - } -} - -void InstructionSelection::prepareCallArgs(IR::ExprList *e, quint32 &argc, quint32 *args) -{ - int argLocation = outgoingArgumentTempStart(); - argc = 0; - if (args) - *args = argLocation; - if (e) { - // We need to move all the temps into the function arg array - Q_ASSERT(argLocation >= 0); - while (e) { - if (IR::Const *c = e->expr->asConst()) { - Instruction::MoveConst move; - move.source = convertToValue(c).asReturnedValue(); - move.result = Param::createTemp(argLocation); - addInstruction(move); - } else { - Instruction::Move move; - move.source = getParam(e->expr); - move.result = Param::createTemp(argLocation); - addInstruction(move); - } - ++argLocation; - ++argc; - e = e->next; - } - } -} - -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) - return; - if (_removableJumps.at(_block->index())) - return; - - addDebugInstruction(); - - Instruction::Jump jump; - jump.offset = 0; - ptrdiff_t loc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); - - _patches[s->target].append(loc); -} - -void InstructionSelection::visitCJump(IR::CJump *s) -{ - addDebugInstruction(); - - Param condition; - if (IR::Temp *t = s->cond->asTemp()) { - condition = getResultParam(t); - } else if (IR::Binop *b = s->cond->asBinop()) { - condition = binopHelper(b->op, b->left, b->right, /*target*/0); - } else { - Q_UNIMPLEMENTED(); - } - - if (s->iftrue == _nextBlock) { - Instruction::JumpNe jump; - jump.offset = 0; - jump.condition = condition; - ptrdiff_t falseLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); - _patches[s->iffalse].append(falseLoc); - } else { - Instruction::JumpEq jump; - jump.offset = 0; - jump.condition = condition; - ptrdiff_t trueLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); - _patches[s->iftrue].append(trueLoc); - - if (s->iffalse != _nextBlock) { - Instruction::Jump jump; - jump.offset = 0; - ptrdiff_t falseLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); - _patches[s->iffalse].append(falseLoc); - } - } -} - -void InstructionSelection::visitRet(IR::Ret *s) -{ - // this is required so stepOut will always be guaranteed to stop in every stack frame - addDebugInstruction(); - - Instruction::Ret ret; - ret.result = getParam(s->expr); - addInstruction(ret); -} - -void InstructionSelection::callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Expr *result) -{ - if (useFastLookups && func->global) { - Instruction::CallGlobalLookup call; - call.index = registerGlobalGetterLookup(*func->id); - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.result = getResultParam(result); - addInstruction(call); - return; - } - Instruction::CallActivationProperty call; - call.name = registerString(*func->id); - prepareCallArgs(args, call.argc); - call.callData = callDataStart(); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinTypeofQmlContextProperty(IR::Expr *base, IR::Member::MemberKind kind, int propertyIndex, IR::Expr *result) -{ - if (kind == IR::Member::MemberOfQmlScopeObject) { - Instruction::CallBuiltinTypeofScopeObjectProperty call; - call.base = getParam(base); - call.index = propertyIndex; - call.result = getResultParam(result); - addInstruction(call); - } else if (kind == IR::Member::MemberOfQmlContextObject) { - Instruction::CallBuiltinTypeofContextObjectProperty call; - call.base = getParam(base); - call.index = propertyIndex; - call.result = getResultParam(result); - addInstruction(call); - } else { - Q_UNREACHABLE(); - } -} - -void InstructionSelection::callBuiltinTypeofMember(IR::Expr *base, const QString &name, - IR::Expr *result) -{ - Instruction::CallBuiltinTypeofMember call; - call.base = getParam(base); - call.member = registerString(name); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinTypeofSubscript(IR::Expr *base, IR::Expr *index, - IR::Expr *result) -{ - Instruction::CallBuiltinTypeofSubscript call; - call.base = getParam(base); - call.index = getParam(index); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinTypeofName(const QString &name, IR::Expr *result) -{ - Instruction::CallBuiltinTypeofName call; - call.name = registerString(name); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinTypeofValue(IR::Expr *value, IR::Expr *result) -{ - Instruction::CallBuiltinTypeofValue call; - call.value = getParam(value); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinDeleteMember(IR::Expr *base, const QString &name, IR::Expr *result) -{ - Instruction::CallBuiltinDeleteMember call; - call.base = getParam(base); - call.member = registerString(name); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinDeleteSubscript(IR::Expr *base, IR::Expr *index, - IR::Expr *result) -{ - Instruction::CallBuiltinDeleteSubscript call; - call.base = getParam(base); - call.index = getParam(index); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinDeleteName(const QString &name, IR::Expr *result) -{ - Instruction::CallBuiltinDeleteName call; - call.name = registerString(name); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinDeleteValue(IR::Expr *result) -{ - Instruction::MoveConst move; - move.source = QV4::Encode(false); - move.result = getResultParam(result); - addInstruction(move); -} - -void InstructionSelection::callBuiltinThrow(IR::Expr *arg) -{ - Instruction::CallBuiltinThrow call; - call.arg = getParam(arg); - addInstruction(call); -} - -void InstructionSelection::callBuiltinReThrow() -{ - if (_block->catchBlock) { - // jump to exception handler - Instruction::Jump jump; - jump.offset = 0; - ptrdiff_t loc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); - - _patches[_block->catchBlock].append(loc); - } else { - Instruction::Ret ret; - int idx = jsUnitGenerator()->registerConstant(QV4::Encode::undefined()); - ret.result = Param::createConstant(idx); - addInstruction(ret); - } -} - -void InstructionSelection::callBuiltinUnwindException(IR::Expr *result) -{ - Instruction::CallBuiltinUnwindException call; - call.result = getResultParam(result); - addInstruction(call); -} - - -void InstructionSelection::callBuiltinPushCatchScope(const QString &exceptionName) -{ - Instruction::CallBuiltinPushCatchScope call; - call.name = registerString(exceptionName); - addInstruction(call); -} - -void InstructionSelection::callBuiltinForeachIteratorObject(IR::Expr *arg, IR::Expr *result) -{ - Instruction::CallBuiltinForeachIteratorObject call; - call.arg = getParam(arg); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinForeachNextPropertyname(IR::Expr *arg, IR::Expr *result) -{ - Instruction::CallBuiltinForeachNextPropertyName call; - call.arg = getParam(arg); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinPushWithScope(IR::Expr *arg) -{ - Instruction::CallBuiltinPushScope call; - call.arg = getParam(arg); - addInstruction(call); -} - -void InstructionSelection::callBuiltinPopScope() -{ - QT_WARNING_PUSH - QT_WARNING_DISABLE_GCC("-Wuninitialized") - Instruction::CallBuiltinPopScope call; - addInstruction(call); - QT_WARNING_POP -} - -void InstructionSelection::callBuiltinDeclareVar(bool deletable, const QString &name) -{ - Instruction::CallBuiltinDeclareVar call; - call.isDeletable = deletable; - call.varName = registerString(name); - addInstruction(call); -} - -void InstructionSelection::callBuiltinDefineArray(IR::Expr *result, IR::ExprList *args) -{ - Instruction::CallBuiltinDefineArray call; - prepareCallArgs(args, call.argc, &call.args); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinDefineObjectLiteral(IR::Expr *result, int keyValuePairCount, IR::ExprList *keyValuePairs, IR::ExprList *arrayEntries, bool needSparseArray) -{ - int argLocation = outgoingArgumentTempStart(); - - const int classId = registerJSClass(keyValuePairCount, keyValuePairs); - - // Process key/value pairs first - IR::ExprList *it = keyValuePairs; - for (int i = 0; i < keyValuePairCount; ++i, it = it->next) { - // Skip name - it = it->next; - - bool isData = it->expr->asConst()->value; - it = it->next; - - if (IR::Const *c = it->expr->asConst()) { - Instruction::MoveConst move; - move.source = convertToValue(c).asReturnedValue(); - move.result = Param::createTemp(argLocation); - addInstruction(move); - } else { - Instruction::Move move; - move.source = getParam(it->expr); - move.result = Param::createTemp(argLocation); - addInstruction(move); - } - ++argLocation; - - if (!isData) { - it = it->next; - - Instruction::Move move; - move.source = getParam(it->expr); - move.result = Param::createTemp(argLocation); - addInstruction(move); - ++argLocation; - } - } - - // Process array values - uint arrayValueCount = 0; - it = arrayEntries; - while (it) { - IR::Const *index = it->expr->asConst(); - it = it->next; - - bool isData = it->expr->asConst()->value; - it = it->next; - - if (!isData) { - it = it->next; // getter - it = it->next; // setter - continue; - } - - ++arrayValueCount; - - Instruction::MoveConst indexMove; - indexMove.source = convertToValue(index).asReturnedValue(); - indexMove.result = Param::createTemp(argLocation); - addInstruction(indexMove); - ++argLocation; - - Instruction::Move move; - move.source = getParam(it->expr); - move.result = Param::createTemp(argLocation); - addInstruction(move); - ++argLocation; - it = it->next; - } - - // Process array getter/setter pairs - uint arrayGetterSetterCount = 0; - it = arrayEntries; - while (it) { - IR::Const *index = it->expr->asConst(); - it = it->next; - - bool isData = it->expr->asConst()->value; - it = it->next; - - if (isData) { - it = it->next; // value - continue; - } - - ++arrayGetterSetterCount; - - Instruction::MoveConst indexMove; - indexMove.source = convertToValue(index).asReturnedValue(); - indexMove.result = Param::createTemp(argLocation); - addInstruction(indexMove); - ++argLocation; - - // getter - Instruction::Move moveGetter; - moveGetter.source = getParam(it->expr); - moveGetter.result = Param::createTemp(argLocation); - addInstruction(moveGetter); - ++argLocation; - it = it->next; - - // setter - Instruction::Move moveSetter; - moveSetter.source = getParam(it->expr); - moveSetter.result = Param::createTemp(argLocation); - addInstruction(moveSetter); - ++argLocation; - it = it->next; - } - - Instruction::CallBuiltinDefineObjectLiteral call; - call.internalClassId = classId; - call.arrayValueCount = arrayValueCount; - call.arrayGetterSetterCountAndFlags = arrayGetterSetterCount | (needSparseArray << 30); - call.args = outgoingArgumentTempStart(); - call.result = getResultParam(result); - addInstruction(call); -} - -void InstructionSelection::callBuiltinSetupArgumentObject(IR::Expr *result) -{ - Instruction::CallBuiltinSetupArgumentsObject call; - call.result = getResultParam(result); - addInstruction(call); -} - - -void QV4::Moth::InstructionSelection::callBuiltinConvertThisToObject() -{ - QT_WARNING_PUSH - QT_WARNING_DISABLE_GCC("-Wuninitialized") - Instruction::CallBuiltinConvertThisToObject call; - addInstruction(call); - QT_WARNING_POP -} - -ptrdiff_t InstructionSelection::addInstructionHelper(Instr::Type type, Instr &instr) -{ - instr.common.instructionType = type; - - int instructionSize = Instr::size(type); - if (_codeEnd - _codeNext < instructionSize) { - int currSize = _codeEnd - _codeStart; - uchar *newCode = new uchar[currSize * 2]; - ::memset(newCode + currSize, 0, currSize); - ::memcpy(newCode, _codeStart, currSize); - _codeNext = _codeNext - _codeStart + newCode; - delete[] _codeStart; - _codeStart = newCode; - _codeEnd = _codeStart + currSize * 2; - } - - ::memcpy(_codeNext, reinterpret_cast<const char *>(&instr), instructionSize); - ptrdiff_t ptrOffset = _codeNext - _codeStart; - _codeNext += instructionSize; - - return ptrOffset; -} - -void InstructionSelection::patchJumpAddresses() -{ - typedef QHash<IR::BasicBlock *, QVector<ptrdiff_t> >::ConstIterator PatchIt; - for (PatchIt i = _patches.cbegin(), ei = _patches.cend(); i != ei; ++i) { - Q_ASSERT(_addrs.contains(i.key())); - ptrdiff_t target = _addrs.value(i.key()); - - const QVector<ptrdiff_t> &patchList = i.value(); - for (int ii = 0, eii = patchList.count(); ii < eii; ++ii) { - ptrdiff_t patch = patchList.at(ii); - - *((ptrdiff_t *)(_codeStart + patch)) = target - patch; - } - } - - _patches.clear(); - _addrs.clear(); -} - -QByteArray InstructionSelection::squeezeCode() const -{ - int codeSize = _codeNext - _codeStart; - QByteArray squeezed; - squeezed.resize(codeSize); - ::memcpy(squeezed.data(), _codeStart, codeSize); - return squeezed; -} - -Param InstructionSelection::getParam(IR::Expr *e) { - Q_ASSERT(e); - - if (IR::Const *c = e->asConst()) { - int idx = jsUnitGenerator()->registerConstant(convertToValue(c).asReturnedValue()); - return Param::createConstant(idx); - } else if (IR::Temp *t = e->asTemp()) { - switch (t->kind) { - case IR::Temp::StackSlot: - return Param::createTemp(t->index); - default: - Q_UNREACHABLE(); - return Param(); - } - } else if (IR::ArgLocal *al = e->asArgLocal()) { - switch (al->kind) { - case IR::ArgLocal::Formal: - case IR::ArgLocal::ScopedFormal: return Param::createArgument(al->index, al->scope); - case IR::ArgLocal::Local: return Param::createLocal(al->index); - case IR::ArgLocal::ScopedLocal: return Param::createScopedLocal(al->index, al->scope); - default: - Q_UNREACHABLE(); - return Param(); - } - } else { - Q_UNIMPLEMENTED(); - return Param(); - } -} - - -CompilationUnit::~CompilationUnit() -{ -} - -#if !defined(V4_BOOTSTRAP) - -void CompilationUnit::linkBackendToEngine(QV4::ExecutionEngine *engine) -{ - runtimeFunctions.resize(data->functionTableSize); - runtimeFunctions.fill(0); - for (int i = 0 ;i < runtimeFunctions.size(); ++i) { - const QV4::CompiledData::Function *compiledFunction = data->functionAt(i); - - QV4::Function *runtimeFunction = new QV4::Function(engine, this, compiledFunction, &VME::exec); - runtimeFunction->codeData = reinterpret_cast<const uchar *>(codeRefs.at(i).constData()); - runtimeFunctions[i] = runtimeFunction; - } -} - -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; -} - -#endif // V4_BOOTSTRAP - -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; - - 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); - - written = device->write(code.constData(), compiledFunction->codeSize); - if (written != qint64(compiledFunction->codeSize)) { - *errorString = device->errorString(); - return false; - } - } - 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 deleted file mode 100644 index 4b84bd2831..0000000000 --- a/src/qml/compiler/qv4isel_moth_p.h +++ /dev/null @@ -1,240 +0,0 @@ -/**************************************************************************** -** -** 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 QV4ISEL_MOTH_P_H -#define QV4ISEL_MOTH_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <private/qv4global_p.h> -#include <private/qv4isel_p.h> -#include <private/qv4isel_util_p.h> -#include <private/qv4util_p.h> -#include <private/qv4jsir_p.h> -#include <private/qv4value_p.h> -#include "qv4instr_moth_p.h" - -#if !defined(V4_BOOTSTRAP) -QT_REQUIRE_CONFIG(qml_interpreter); -#endif - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace Moth { - -struct CompilationUnit : public QV4::CompiledData::CompilationUnit -{ - virtual ~CompilationUnit(); -#if !defined(V4_BOOTSTRAP) - void linkBackendToEngine(QV4::ExecutionEngine *engine) Q_DECL_OVERRIDE; - bool memoryMapCode(QString *errorString) Q_DECL_OVERRIDE; -#endif - void prepareCodeOffsetsForDiskStorage(CompiledData::Unit *unit) Q_DECL_OVERRIDE; - bool saveCodeToDisk(QIODevice *device, const CompiledData::Unit *unit, QString *errorString) Q_DECL_OVERRIDE; - - QVector<QByteArray> codeRefs; - -}; - -class Q_QML_EXPORT InstructionSelection: - public IR::IRDecoder, - public EvalInstructionSelection -{ -public: - InstructionSelection(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator, EvalISelFactory *iselFactory); - ~InstructionSelection(); - - void run(int functionIndex) override; - -protected: - QQmlRefPointer<CompiledData::CompilationUnit> backendCompileStep() override; - - void visitJump(IR::Jump *) override; - void visitCJump(IR::CJump *) override; - void visitRet(IR::Ret *) override; - - void callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Expr *result) override; - void callBuiltinTypeofQmlContextProperty(IR::Expr *base, IR::Member::MemberKind kind, int propertyIndex, IR::Expr *result) override; - void callBuiltinTypeofMember(IR::Expr *base, const QString &name, IR::Expr *result) override; - void callBuiltinTypeofSubscript(IR::Expr *base, IR::Expr *index, IR::Expr *result) override; - void callBuiltinTypeofName(const QString &name, IR::Expr *result) override; - void callBuiltinTypeofValue(IR::Expr *value, IR::Expr *result) override; - void callBuiltinDeleteMember(IR::Expr *base, const QString &name, IR::Expr *result) override; - void callBuiltinDeleteSubscript(IR::Expr *base, IR::Expr *index, IR::Expr *result) override; - void callBuiltinDeleteName(const QString &name, IR::Expr *result) override; - void callBuiltinDeleteValue(IR::Expr *result) override; - void callBuiltinThrow(IR::Expr *arg) override; - void callBuiltinReThrow() override; - void callBuiltinUnwindException(IR::Expr *) override; - void callBuiltinPushCatchScope(const QString &exceptionName) override; - void callBuiltinForeachIteratorObject(IR::Expr *arg, IR::Expr *result) override; - void callBuiltinForeachNextPropertyname(IR::Expr *arg, IR::Expr *result) override; - void callBuiltinPushWithScope(IR::Expr *arg) override; - void callBuiltinPopScope() override; - void callBuiltinDeclareVar(bool deletable, const QString &name) override; - void callBuiltinDefineArray(IR::Expr *result, IR::ExprList *args) override; - void callBuiltinDefineObjectLiteral(IR::Expr *result, int keyValuePairCount, IR::ExprList *keyValuePairs, IR::ExprList *arrayEntries, bool needSparseArray) override; - void callBuiltinSetupArgumentObject(IR::Expr *result) override; - void callBuiltinConvertThisToObject() override; - void callValue(IR::Expr *value, IR::ExprList *args, IR::Expr *result) override; - void callQmlContextProperty(IR::Expr *base, IR::Member::MemberKind kind, int propertyIndex, IR::ExprList *args, IR::Expr *result) override; - void callProperty(IR::Expr *base, const QString &name, IR::ExprList *args, IR::Expr *result) override; - void callSubscript(IR::Expr *base, IR::Expr *index, IR::ExprList *args, IR::Expr *result) override; - void convertType(IR::Expr *source, IR::Expr *target) override; - void constructActivationProperty(IR::Name *func, IR::ExprList *args, IR::Expr *result) override; - void constructProperty(IR::Expr *base, const QString &name, IR::ExprList *args, IR::Expr *result) override; - void constructValue(IR::Expr *value, IR::ExprList *args, IR::Expr *result) override; - void loadThisObject(IR::Expr *e) override; - void loadQmlContext(IR::Expr *e) override; - void loadQmlImportedScripts(IR::Expr *e) override; - void loadQmlSingleton(const QString &name, IR::Expr *e) override; - void loadConst(IR::Const *sourceConst, IR::Expr *e) override; - void loadString(const QString &str, IR::Expr *target) override; - void loadRegexp(IR::RegExp *sourceRegexp, IR::Expr *target) override; - void getActivationProperty(const IR::Name *name, IR::Expr *target) override; - void setActivationProperty(IR::Expr *source, const QString &targetName) override; - void initClosure(IR::Closure *closure, IR::Expr *target) override; - void getProperty(IR::Expr *base, const QString &name, IR::Expr *target) override; - void setProperty(IR::Expr *source, IR::Expr *targetBase, const QString &targetName) override; - void setQmlContextProperty(IR::Expr *source, IR::Expr *targetBase, IR::Member::MemberKind kind, int propertyIndex) override; - void setQObjectProperty(IR::Expr *source, IR::Expr *targetBase, int propertyIndex) override; - void getQmlContextProperty(IR::Expr *source, IR::Member::MemberKind kind, int index, bool captureRequired, IR::Expr *target) override; - void getQObjectProperty(IR::Expr *base, int propertyIndex, bool captureRequired, bool isSingleton, int attachedPropertiesId, IR::Expr *target) override; - void getElement(IR::Expr *base, IR::Expr *index, IR::Expr *target) override; - void setElement(IR::Expr *source, IR::Expr *targetBase, IR::Expr *targetIndex) override; - void copyValue(IR::Expr *source, IR::Expr *target) override; - void swapValues(IR::Expr *source, IR::Expr *target) override; - void unop(IR::AluOp oper, IR::Expr *source, IR::Expr *target) override; - void binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Expr *target) override; - -private: - Param binopHelper(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Expr *target); - - struct Instruction { -#define MOTH_INSTR_DATA_TYPEDEF(I, FMT) typedef InstrData<Instr::I> I; - FOR_EACH_MOTH_INSTR(MOTH_INSTR_DATA_TYPEDEF) -#undef MOTH_INSTR_DATA_TYPEDEF - private: - Instruction(); - }; - - Param getParam(IR::Expr *e); - - Param getResultParam(IR::Expr *result) - { - if (result) - return getParam(result); - else - return Param::createTemp(scratchTempIndex()); - } - - void simpleMove(IR::Move *); - void prepareCallArgs(IR::ExprList *, quint32 &, quint32 * = 0); - - int scratchTempIndex() const { return _function->tempCount; } - int callDataStart() const { return scratchTempIndex() + 1; } - int outgoingArgumentTempStart() const { return callDataStart() + offsetof(QV4::CallData, args)/sizeof(QV4::Value); } - int frameSize() const { return outgoingArgumentTempStart() + _function->maxNumberOfArguments; } - - 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; - - QQmlEnginePrivate *qmlEngine; - - bool blockNeedsDebugInstruction; - uint currentLine; - IR::BasicBlock *_block; - IR::BasicBlock *_nextBlock; - - QHash<IR::BasicBlock *, QVector<ptrdiff_t> > _patches; - QHash<IR::BasicBlock *, ptrdiff_t> _addrs; - - uchar *_codeStart; - uchar *_codeNext; - uchar *_codeEnd; - - BitVector _removableJumps; - IR::Stmt *_currentStatement; - - QScopedPointer<CompilationUnit> compilationUnit; - QHash<IR::Function *, QByteArray> codeRefs; -}; - -class Q_QML_EXPORT ISelFactory: public EvalISelFactory -{ -public: - ISelFactory() : EvalISelFactory(QStringLiteral("moth")) {} - virtual ~ISelFactory() {} - 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> -ptrdiff_t InstructionSelection::addInstruction(const InstrData<InstrT> &data) -{ - Instr genericInstr; - InstrMeta<InstrT>::setDataNoCommon(genericInstr, data); - return addInstructionHelper(static_cast<Instr::Type>(InstrT), genericInstr); -} - -} // namespace Moth -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4ISEL_MOTH_P_H diff --git a/src/qml/compiler/qv4isel_p.cpp b/src/qml/compiler/qv4isel_p.cpp deleted file mode 100644 index efcfb9bd77..0000000000 --- a/src/qml/compiler/qv4isel_p.cpp +++ /dev/null @@ -1,446 +0,0 @@ -/**************************************************************************** -** -** 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 <QtCore/QDebug> -#include <QtCore/QBuffer> -#include "qv4jsir_p.h" -#include "qv4isel_p.h" -#include "qv4isel_util_p.h" -#include <private/qv4value_p.h> -#ifndef V4_BOOTSTRAP -#include <private/qqmlpropertycache_p.h> -#endif - -#include <QString> - -using namespace QV4; -using namespace QV4::IR; - -EvalInstructionSelection::EvalInstructionSelection(QV4::ExecutableAllocator *execAllocator, Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator, EvalISelFactory *iselFactory) - : useFastLookups(true) - , useTypeInference(true) - , executableAllocator(execAllocator) - , irModule(module) -{ - if (!jsGenerator) { - jsGenerator = new QV4::Compiler::JSUnitGenerator(module); - ownJSGenerator.reset(jsGenerator); - } - this->jsGenerator = jsGenerator; -#ifndef V4_BOOTSTRAP - Q_ASSERT(execAllocator); -#endif - Q_ASSERT(module); - jsGenerator->codeGeneratorName = iselFactory->codeGeneratorName; -} - -EvalInstructionSelection::~EvalInstructionSelection() -{} - -EvalISelFactory::~EvalISelFactory() -{} - -QQmlRefPointer<CompiledData::CompilationUnit> EvalInstructionSelection::compile(bool generateUnitData) -{ - for (int i = 0; i < irModule->functions.size(); ++i) - run(i); - - QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit = backendCompileStep(); - if (generateUnitData) - unit->data = jsGenerator->generateUnit(); - return unit; -} - -void IRDecoder::visitMove(IR::Move *s) -{ - if (IR::Name *n = s->target->asName()) { - if (s->source->asTemp() || s->source->asConst() || s->source->asArgLocal()) { - setActivationProperty(s->source, *n->id); - return; - } - } else if (s->target->asTemp() || s->target->asArgLocal()) { - if (IR::Name *n = s->source->asName()) { - if (n->id && *n->id == QLatin1String("this")) // TODO: `this' should be a builtin. - loadThisObject(s->target); - else if (n->builtin == IR::Name::builtin_qml_context) - loadQmlContext(s->target); - else if (n->builtin == IR::Name::builtin_qml_imported_scripts_object) - loadQmlImportedScripts(s->target); - else if (n->qmlSingleton) - loadQmlSingleton(*n->id, s->target); - else - getActivationProperty(n, s->target); - return; - } else if (IR::Const *c = s->source->asConst()) { - loadConst(c, s->target); - return; - } else if (s->source->asTemp() || s->source->asArgLocal()) { - if (s->swap) - swapValues(s->source, s->target); - else - copyValue(s->source, s->target); - return; - } else if (IR::String *str = s->source->asString()) { - loadString(*str->value, s->target); - return; - } else if (IR::RegExp *re = s->source->asRegExp()) { - loadRegexp(re, s->target); - return; - } else if (IR::Closure *clos = s->source->asClosure()) { - initClosure(clos, s->target); - return; - } else if (IR::New *ctor = s->source->asNew()) { - if (Name *func = ctor->base->asName()) { - constructActivationProperty(func, ctor->args, s->target); - return; - } else if (IR::Member *member = ctor->base->asMember()) { - constructProperty(member->base, *member->name, ctor->args, s->target); - return; - } else if (ctor->base->asTemp() || ctor->base->asArgLocal()) { - constructValue(ctor->base, ctor->args, s->target); - return; - } - } else if (IR::Member *m = s->source->asMember()) { - if (m->property) { -#ifdef V4_BOOTSTRAP - Q_UNIMPLEMENTED(); -#else - bool captureRequired = true; - - Q_ASSERT(m->kind != IR::Member::MemberOfEnum && m->kind != IR::Member::MemberOfIdObjectsArray); - const int attachedPropertiesId = m->attachedPropertiesId; - const bool isSingletonProperty = m->kind == IR::Member::MemberOfSingletonObject; - - if (_function && attachedPropertiesId == 0 && !m->property->isConstant() && _function->isQmlBinding) { - if (m->kind == IR::Member::MemberOfQmlContextObject) { - _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()); - captureRequired = false; - } - } - if (m->kind == IR::Member::MemberOfQmlScopeObject || m->kind == IR::Member::MemberOfQmlContextObject) { - 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); -#endif // V4_BOOTSTRAP - return; - } else if (m->kind == IR::Member::MemberOfIdObjectsArray) { - 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); - return; - } - } else if (IR::Subscript *ss = s->source->asSubscript()) { - getElement(ss->base, ss->index, s->target); - return; - } else if (IR::Unop *u = s->source->asUnop()) { - unop(u->op, u->expr, s->target); - return; - } else if (IR::Binop *b = s->source->asBinop()) { - binop(b->op, b->left, b->right, s->target); - return; - } else if (IR::Call *c = s->source->asCall()) { - if (c->base->asName()) { - callBuiltin(c, s->target); - return; - } else if (Member *member = c->base->asMember()) { -#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); - return; - } -#endif - callProperty(member->base, *member->name, c->args, s->target); - return; - } else if (Subscript *ss = c->base->asSubscript()) { - callSubscript(ss->base, ss->index, c->args, s->target); - return; - } else if (c->base->asTemp() || c->base->asArgLocal() || c->base->asConst()) { - callValue(c->base, c->args, s->target); - return; - } - } else if (IR::Convert *c = s->source->asConvert()) { - Q_ASSERT(c->expr->asTemp() || c->expr->asArgLocal()); - convertType(c->expr, s->target); - return; - } - } else if (IR::Member *m = s->target->asMember()) { - if (m->base->asTemp() || m->base->asConst() || m->base->asArgLocal()) { - if (s->source->asTemp() || s->source->asConst() || s->source->asArgLocal()) { - Q_ASSERT(m->kind != IR::Member::MemberOfEnum); - Q_ASSERT(m->kind != IR::Member::MemberOfIdObjectsArray); - const int attachedPropertiesId = m->attachedPropertiesId; - if (m->property && attachedPropertiesId == 0) { -#ifdef V4_BOOTSTRAP - 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()); - return; - } - setQObjectProperty(s->source, m->base, m->property->coreIndex()); -#endif - return; - } else { - setProperty(s->source, m->base, *m->name); - return; - } - } - } - } else if (IR::Subscript *ss = s->target->asSubscript()) { - if (s->source->asTemp() || s->source->asConst() || s->source->asArgLocal()) { - setElement(s->source, ss->base, ss->index); - return; - } - } - - // For anything else...: - Q_UNIMPLEMENTED(); - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - IRPrinter(&qout).print(s); - qout << endl; - qDebug("%s", buf.data().constData()); - Q_ASSERT(!"TODO"); -} - -IRDecoder::~IRDecoder() -{ -} - -void IRDecoder::visitExp(IR::Exp *s) -{ - if (IR::Call *c = s->expr->asCall()) { - // These are calls where the result is ignored. - if (c->base->asName()) { - callBuiltin(c, 0); - } else if (c->base->asTemp() || c->base->asArgLocal() || c->base->asConst()) { - callValue(c->base, c->args, 0); - } else if (Member *member = c->base->asMember()) { - Q_ASSERT(member->base->asTemp() || member->base->asArgLocal()); -#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); - return; - } -#endif - callProperty(member->base, *member->name, c->args, 0); - } else if (Subscript *s = c->base->asSubscript()) { - callSubscript(s->base, s->index, c->args, 0); - } else { - Q_UNREACHABLE(); - } - } else { - Q_UNREACHABLE(); - } -} - -void IRDecoder::callBuiltin(IR::Call *call, Expr *result) -{ - IR::Name *baseName = call->base->asName(); - Q_ASSERT(baseName != 0); - - switch (baseName->builtin) { - case IR::Name::builtin_invalid: - callBuiltinInvalid(baseName, call->args, result); - return; - - case IR::Name::builtin_typeof: { - if (IR::Member *member = call->args->expr->asMember()) { -#ifndef V4_BOOTSTRAP - Q_ASSERT(member->kind != IR::Member::MemberOfIdObjectsArray); - if (member->kind == IR::Member::MemberOfQmlScopeObject || member->kind == IR::Member::MemberOfQmlContextObject) { - callBuiltinTypeofQmlContextProperty(member->base, - IR::Member::MemberKind(member->kind), - member->property->coreIndex(), result); - return; - } -#endif - callBuiltinTypeofMember(member->base, *member->name, result); - return; - } else if (IR::Subscript *ss = call->args->expr->asSubscript()) { - callBuiltinTypeofSubscript(ss->base, ss->index, result); - return; - } else if (IR::Name *n = call->args->expr->asName()) { - callBuiltinTypeofName(*n->id, result); - return; - } else if (call->args->expr->asTemp() || - call->args->expr->asConst() || - call->args->expr->asArgLocal()) { - callBuiltinTypeofValue(call->args->expr, result); - return; - } - } break; - - case IR::Name::builtin_delete: { - if (IR::Member *m = call->args->expr->asMember()) { - callBuiltinDeleteMember(m->base, *m->name, result); - return; - } else if (IR::Subscript *ss = call->args->expr->asSubscript()) { - callBuiltinDeleteSubscript(ss->base, ss->index, result); - return; - } else if (IR::Name *n = call->args->expr->asName()) { - callBuiltinDeleteName(*n->id, result); - return; - } else if (call->args->expr->asTemp() || - call->args->expr->asArgLocal()) { - // TODO: should throw in strict mode - callBuiltinDeleteValue(result); - return; - } - } break; - - case IR::Name::builtin_throw: { - IR::Expr *arg = call->args->expr; - Q_ASSERT(arg->asTemp() || arg->asConst() || arg->asArgLocal()); - callBuiltinThrow(arg); - } return; - - case IR::Name::builtin_rethrow: { - callBuiltinReThrow(); - } return; - - case IR::Name::builtin_unwind_exception: { - callBuiltinUnwindException(result); - } return; - - case IR::Name::builtin_push_catch_scope: { - IR::String *s = call->args->expr->asString(); - Q_ASSERT(s); - callBuiltinPushCatchScope(*s->value); - } return; - - case IR::Name::builtin_foreach_iterator_object: { - IR::Expr *arg = call->args->expr; - Q_ASSERT(arg != 0); - callBuiltinForeachIteratorObject(arg, result); - } return; - - case IR::Name::builtin_foreach_next_property_name: { - IR::Expr *arg = call->args->expr; - Q_ASSERT(arg != 0); - callBuiltinForeachNextPropertyname(arg, result); - } return; - case IR::Name::builtin_push_with_scope: { - if (call->args->expr->asTemp() || call->args->expr->asArgLocal()) - callBuiltinPushWithScope(call->args->expr); - else - Q_UNIMPLEMENTED(); - } return; - - case IR::Name::builtin_pop_scope: - callBuiltinPopScope(); - return; - - case IR::Name::builtin_declare_vars: { - if (!call->args) - return; - IR::Const *deletable = call->args->expr->asConst(); - Q_ASSERT(deletable->type == IR::BoolType); - for (IR::ExprList *it = call->args->next; it; it = it->next) { - IR::Name *arg = it->expr->asName(); - Q_ASSERT(arg != 0); - callBuiltinDeclareVar(deletable->value != 0, *arg->id); - } - } return; - - case IR::Name::builtin_define_array: - callBuiltinDefineArray(result, call->args); - return; - - case IR::Name::builtin_define_object_literal: { - IR::ExprList *args = call->args; - const int keyValuePairsCount = args->expr->asConst()->value; - args = args->next; - - IR::ExprList *keyValuePairs = args; - for (int i = 0; i < keyValuePairsCount; ++i) { - args = args->next; // name - bool isData = args->expr->asConst()->value; - args = args->next; // isData flag - args = args->next; // value or getter - if (!isData) - args = args->next; // setter - } - - IR::ExprList *arrayEntries = args; - bool needSparseArray = false; - for (IR::ExprList *it = arrayEntries; it; it = it->next) { - uint index = it->expr->asConst()->value; - if (index > 16) { - needSparseArray = true; - break; - } - it = it->next; - bool isData = it->expr->asConst()->value; - it = it->next; - if (!isData) - it = it->next; - } - - callBuiltinDefineObjectLiteral(result, keyValuePairsCount, keyValuePairs, arrayEntries, needSparseArray); - } return; - - case IR::Name::builtin_setup_argument_object: - callBuiltinSetupArgumentObject(result); - return; - - case IR::Name::builtin_convert_this_to_object: - callBuiltinConvertThisToObject(); - return; - - default: - break; - } - - Q_UNIMPLEMENTED(); - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - IRPrinter(&qout).print(call); qout << endl; - qDebug("%s", buf.data().constData()); - Q_UNREACHABLE(); -} diff --git a/src/qml/compiler/qv4isel_p.h b/src/qml/compiler/qv4isel_p.h deleted file mode 100644 index 037c02e5ea..0000000000 --- a/src/qml/compiler/qv4isel_p.h +++ /dev/null @@ -1,218 +0,0 @@ -/**************************************************************************** -** -** 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 QV4ISEL_P_H -#define QV4ISEL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "private/qv4global_p.h" -#include "qv4jsir_p.h" -#include <private/qv4compileddata_p.h> -#include <private/qv4compiler_p.h> - -#include <qglobal.h> -#include <QHash> - -QT_BEGIN_NAMESPACE - -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, EvalISelFactory *iselFactory); - virtual ~EvalInstructionSelection() = 0; - - QQmlRefPointer<QV4::CompiledData::CompilationUnit> compile(bool generateUnitData = true); - - void setUseFastLookups(bool b) { useFastLookups = b; } - void setUseTypeInference(bool onoff) { useTypeInference = onoff; } - - int registerString(const QString &str) { return jsGenerator->registerString(str); } - uint registerIndexedGetterLookup() { return jsGenerator->registerIndexedGetterLookup(); } - uint registerIndexedSetterLookup() { return jsGenerator->registerIndexedSetterLookup(); } - uint registerGetterLookup(const QString &name) { return jsGenerator->registerGetterLookup(name); } - uint registerSetterLookup(const QString &name) { return jsGenerator->registerSetterLookup(name); } - uint registerGlobalGetterLookup(const QString &name) { return jsGenerator->registerGlobalGetterLookup(name); } - int registerRegExp(IR::RegExp *regexp) { return jsGenerator->registerRegExp(regexp); } - int registerJSClass(int count, IR::ExprList *args) { return jsGenerator->registerJSClass(count, args); } - QV4::Compiler::JSUnitGenerator *jsUnitGenerator() const { return jsGenerator; } - -protected: - virtual void run(int functionIndex) = 0; - virtual QQmlRefPointer<QV4::CompiledData::CompilationUnit> backendCompileStep() = 0; - - bool useFastLookups; - bool useTypeInference; - QV4::ExecutableAllocator *executableAllocator; - QV4::Compiler::JSUnitGenerator *jsGenerator; - QScopedPointer<QV4::Compiler::JSUnitGenerator> ownJSGenerator; - IR::Module *irModule; -}; - -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 -{ -public: - IRDecoder() : _function(0) {} - virtual ~IRDecoder() = 0; - - 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; - virtual void callBuiltinTypeofQmlContextProperty(IR::Expr *base, IR::Member::MemberKind kind, int propertyIndex, IR::Expr *result) = 0; - virtual void callBuiltinTypeofMember(IR::Expr *base, const QString &name, IR::Expr *result) = 0; - virtual void callBuiltinTypeofSubscript(IR::Expr *base, IR::Expr *index, IR::Expr *result) = 0; - virtual void callBuiltinTypeofName(const QString &name, IR::Expr *result) = 0; - virtual void callBuiltinTypeofValue(IR::Expr *value, IR::Expr *result) = 0; - virtual void callBuiltinDeleteMember(IR::Expr *base, const QString &name, IR::Expr *result) = 0; - virtual void callBuiltinDeleteSubscript(IR::Expr *base, IR::Expr *index, IR::Expr *result) = 0; - virtual void callBuiltinDeleteName(const QString &name, IR::Expr *result) = 0; - virtual void callBuiltinDeleteValue(IR::Expr *result) = 0; - virtual void callBuiltinThrow(IR::Expr *arg) = 0; - virtual void callBuiltinReThrow() = 0; - virtual void callBuiltinUnwindException(IR::Expr *) = 0; - virtual void callBuiltinPushCatchScope(const QString &exceptionName) = 0; - virtual void callBuiltinForeachIteratorObject(IR::Expr *arg, IR::Expr *result) = 0; - virtual void callBuiltinForeachNextPropertyname(IR::Expr *arg, IR::Expr *result) = 0; - virtual void callBuiltinPushWithScope(IR::Expr *arg) = 0; - virtual void callBuiltinPopScope() = 0; - virtual void callBuiltinDeclareVar(bool deletable, const QString &name) = 0; - virtual void callBuiltinDefineArray(IR::Expr *result, IR::ExprList *args) = 0; - virtual void callBuiltinDefineObjectLiteral(IR::Expr *result, int keyValuePairCount, IR::ExprList *keyValuePairs, IR::ExprList *arrayEntries, bool needSparseArray) = 0; - virtual void callBuiltinSetupArgumentObject(IR::Expr *result) = 0; - virtual void callBuiltinConvertThisToObject() = 0; - virtual void callValue(IR::Expr *value, IR::ExprList *args, IR::Expr *result) = 0; - virtual void callQmlContextProperty(IR::Expr *base, IR::Member::MemberKind kind, int propertyIndex, IR::ExprList *args, IR::Expr *result) = 0; - virtual void callProperty(IR::Expr *base, const QString &name, IR::ExprList *args, IR::Expr *result) = 0; - virtual void callSubscript(IR::Expr *base, IR::Expr *index, IR::ExprList *args, IR::Expr *result) = 0; - virtual void convertType(IR::Expr *source, IR::Expr *target) = 0; - virtual void constructActivationProperty(IR::Name *func, IR::ExprList *args, IR::Expr *result) = 0; - virtual void constructProperty(IR::Expr *base, const QString &name, IR::ExprList *args, IR::Expr *result) = 0; - virtual void constructValue(IR::Expr *value, IR::ExprList *args, IR::Expr *result) = 0; - virtual void loadThisObject(IR::Expr *target) = 0; - virtual void loadQmlContext(IR::Expr *target) = 0; - virtual void loadQmlImportedScripts(IR::Expr *target) = 0; - virtual void loadQmlSingleton(const QString &name, IR::Expr *target) = 0; - virtual void loadConst(IR::Const *sourceConst, IR::Expr *target) = 0; - virtual void loadString(const QString &str, IR::Expr *target) = 0; - virtual void loadRegexp(IR::RegExp *sourceRegexp, IR::Expr *target) = 0; - virtual void getActivationProperty(const IR::Name *name, IR::Expr *target) = 0; - virtual void setActivationProperty(IR::Expr *source, const QString &targetName) = 0; - 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, 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; - virtual void getElement(IR::Expr *base, IR::Expr *index, IR::Expr *target) = 0; - virtual void setElement(IR::Expr *source, IR::Expr *targetBase, IR::Expr *targetIndex) = 0; - virtual void copyValue(IR::Expr *source, IR::Expr *target) = 0; - virtual void swapValues(IR::Expr *source, IR::Expr *target) = 0; - virtual void unop(IR::AluOp oper, IR::Expr *source, IR::Expr *target) = 0; - 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 -}; -} // namespace IR - -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4ISEL_P_H diff --git a/src/qml/compiler/qv4isel_util_p.h b/src/qml/compiler/qv4isel_util_p.h deleted file mode 100644 index e949e6f0ad..0000000000 --- a/src/qml/compiler/qv4isel_util_p.h +++ /dev/null @@ -1,241 +0,0 @@ -/**************************************************************************** -** -** 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 QV4ISEL_UTIL_P_H -#define QV4ISEL_UTIL_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/qv4value_p.h" -#include "qv4jsir_p.h" - -QT_BEGIN_NAMESPACE - -namespace QV4 { - -struct TargetPrimitive32 { - static TargetPrimitive32 emptyValue() { TargetPrimitive32 p; p._val = quint64(Value::ValueTypeInternal_32::Empty) << 32; return p; } - static TargetPrimitive32 nullValue() { TargetPrimitive32 p; p._val = quint64(Value::ValueTypeInternal_32::Null) << 32; return p; } - static TargetPrimitive32 undefinedValue() { TargetPrimitive32 p; p._val = quint64(Value::Managed_Type_Internal_32) << 32; return p; } - static TargetPrimitive32 fromBoolean(bool b) { TargetPrimitive32 p; p._val = quint64(Value::ValueTypeInternal_32::Boolean) << 32 | quint64(b); return p; } - static TargetPrimitive32 fromInt32(int v) { TargetPrimitive32 p; p._val = quint64(Value::ValueTypeInternal_32::Integer) << 32 | quint32(v); return p; } - static TargetPrimitive32 fromDouble(double v) { - TargetPrimitive32 p; - memcpy(&p._val, &v, 8); - return p; - } - static TargetPrimitive32 fromUInt32(uint v) { - if (v < INT_MAX) - return fromInt32(qint32(v)); - return fromDouble(double(v)); - } - - quint32 value() const { return _val & quint64(~quint32(0)); } - quint32 tag() const { return _val >> 32; } - - quint64 rawValue() const { return _val; } - -private: - quint64 _val; -}; - -struct TargetPrimitive64 { - static TargetPrimitive64 emptyValue() { TargetPrimitive64 p; p._val = quint64(Value::ValueTypeInternal_64::Empty) << 32; return p; } - static TargetPrimitive64 nullValue() { TargetPrimitive64 p; p._val = quint64(Value::ValueTypeInternal_64::Null) << 32; return p; } - static TargetPrimitive64 undefinedValue() { TargetPrimitive64 p; p._val = 0; return p; } - static TargetPrimitive64 fromBoolean(bool b) { TargetPrimitive64 p; p._val = quint64(Value::ValueTypeInternal_64::Boolean) << 32 | quint64(b); return p; } - static TargetPrimitive64 fromInt32(int v) { TargetPrimitive64 p; p._val = quint64(Value::ValueTypeInternal_64::Integer) << 32 | quint32(v); return p; } - static TargetPrimitive64 fromDouble(double v) { - TargetPrimitive64 p; - memcpy(&p._val, &v, 8); - p._val ^= Value::NaNEncodeMask; - return p; - } - static TargetPrimitive64 fromUInt32(uint v) { - if (v < INT_MAX) - return fromInt32(qint32(v)); - return fromDouble(double(v)); - } - - quint32 value() const { return _val & quint64(~quint32(0)); } - quint32 tag() const { return _val >> 32; } - - quint64 rawValue() const { return _val; } - -private: - quint64 _val; -}; - -inline bool canConvertToSignedInteger(double value) -{ - int ival = (int) value; - // +0 != -0, so we need to convert to double when negating 0 - return ival == value && !(value == 0 && isNegative(value)); -} - -inline bool canConvertToUnsignedInteger(double value) -{ - unsigned uval = (unsigned) value; - // +0 != -0, so we need to convert to double when negating 0 - return uval == value && !(value == 0 && isNegative(value)); -} - -template <typename PrimitiveType = Primitive> -inline PrimitiveType convertToValue(IR::Const *c) -{ - switch (c->type) { - case IR::MissingType: - return PrimitiveType::emptyValue(); - case IR::NullType: - return PrimitiveType::nullValue(); - case IR::UndefinedType: - return PrimitiveType::undefinedValue(); - case IR::BoolType: - return PrimitiveType::fromBoolean(c->value != 0); - case IR::SInt32Type: - return PrimitiveType::fromInt32(int(c->value)); - case IR::UInt32Type: - return PrimitiveType::fromUInt32(unsigned(c->value)); - case IR::DoubleType: - return PrimitiveType::fromDouble(c->value); - case IR::NumberType: { - int ival = (int)c->value; - if (canConvertToSignedInteger(c->value)) { - return PrimitiveType::fromInt32(ival); - } else { - return PrimitiveType::fromDouble(c->value); - } - } - default: - Q_UNREACHABLE(); - } - // unreachable, but the function must return something - return PrimitiveType::undefinedValue(); -} - -class ConvertTemps -{ - void renumber(IR::Temp *t) - { - if (t->kind != IR::Temp::VirtualRegister) - return; - - int stackSlot = _stackSlotForTemp.value(t->index, -1); - if (stackSlot == -1) { - stackSlot = allocateFreeSlot(); - _stackSlotForTemp[t->index] = stackSlot; - } - - t->kind = IR::Temp::StackSlot; - t->index = stackSlot; - } - -protected: - int _nextUnusedStackSlot; - QHash<int, int> _stackSlotForTemp; - IR::BasicBlock *_currentBasicBlock; - virtual int allocateFreeSlot() - { - return _nextUnusedStackSlot++; - } - - virtual void process(IR::Stmt *s) - { - visit(s); - } - -public: - ConvertTemps() - : _nextUnusedStackSlot(0) - , _currentBasicBlock(0) - {} - - void toStackSlots(IR::Function *function) - { - _stackSlotForTemp.reserve(function->tempCount); - - for (IR::BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) - continue; - _currentBasicBlock = bb; - for (IR::Stmt *s : bb->statements()) - process(s); - } - - function->tempCount = _nextUnusedStackSlot; - } - -protected: - 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 visitPhi(IR::Phi *) - { Q_UNREACHABLE(); } - -private: - void visit(IR::Expr *e) { - if (auto temp = e->asTemp()) { - renumber(temp); - } else { - EXPR_VISIT_ALL_KINDS(e); - } - } -}; -} // namespace QV4 - -QT_END_NAMESPACE - -#endif // QV4ISEL_UTIL_P_H diff --git a/src/qml/compiler/qv4jsir.cpp b/src/qml/compiler/qv4jsir.cpp deleted file mode 100644 index 5a58380005..0000000000 --- a/src/qml/compiler/qv4jsir.cpp +++ /dev/null @@ -1,992 +0,0 @@ -/**************************************************************************** -** -** 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 "qv4jsir_p.h" -#include <private/qqmljsast_p.h> - -#ifndef V4_BOOTSTRAP -#include <private/qqmlpropertycache_p.h> -#endif - -#include <QtCore/QBuffer> -#include <QtCore/qtextstream.h> -#include <QtCore/qdebug.h> -#include <QtCore/qset.h> -#include <cmath> - -#include <vector> - -#ifdef CONST -#undef CONST -#endif - -QT_BEGIN_NAMESPACE - -namespace QV4 { -namespace IR { - -QString typeName(Type t) -{ - switch (t) { - case UnknownType: return QStringLiteral(""); - case MissingType: return QStringLiteral("missing"); - case UndefinedType: return QStringLiteral("undefined"); - case NullType: return QStringLiteral("null"); - case BoolType: return QStringLiteral("bool"); - case UInt32Type: return QStringLiteral("uint32"); - case SInt32Type: return QStringLiteral("int32"); - case DoubleType: return QStringLiteral("double"); - case NumberType: return QStringLiteral("number"); - case StringType: return QStringLiteral("string"); - case VarType: return QStringLiteral("var"); - case QObjectType: return QStringLiteral("qobject"); - default: return QStringLiteral("multiple"); - } -} - -const char *opname(AluOp op) -{ - switch (op) { - case OpInvalid: return "?"; - - case OpIfTrue: return "(bool)"; - case OpNot: return "not"; - case OpUMinus: return "neg"; - case OpUPlus: return "plus"; - case OpCompl: return "invert"; - case OpIncrement: return "incr"; - case OpDecrement: return "decr"; - - case OpBitAnd: return "bitand"; - case OpBitOr: return "bitor"; - case OpBitXor: return "bitxor"; - - case OpAdd: return "add"; - case OpSub: return "sub"; - case OpMul: return "mul"; - case OpDiv: return "div"; - case OpMod: return "mod"; - - case OpLShift: return "shl"; - case OpRShift: return "shr"; - case OpURShift: return "asr"; - - case OpGt: return "gt"; - case OpLt: return "lt"; - case OpGe: return "ge"; - case OpLe: return "le"; - case OpEqual: return "eq"; - case OpNotEqual: return "ne"; - case OpStrictEqual: return "se"; - case OpStrictNotEqual: return "sne"; - - case OpInstanceof: return "instanceof"; - case OpIn: return "in"; - - case OpAnd: return "and"; - case OpOr: return "or"; - - default: return "?"; - - } // switch -} - -AluOp binaryOperator(int op) -{ - switch (static_cast<QSOperator::Op>(op)) { - case QSOperator::Add: return OpAdd; - case QSOperator::And: return OpAnd; - case QSOperator::BitAnd: return OpBitAnd; - case QSOperator::BitOr: return OpBitOr; - case QSOperator::BitXor: return OpBitXor; - case QSOperator::Div: return OpDiv; - case QSOperator::Equal: return OpEqual; - case QSOperator::Ge: return OpGe; - case QSOperator::Gt: return OpGt; - case QSOperator::Le: return OpLe; - case QSOperator::LShift: return OpLShift; - case QSOperator::Lt: return OpLt; - case QSOperator::Mod: return OpMod; - case QSOperator::Mul: return OpMul; - case QSOperator::NotEqual: return OpNotEqual; - case QSOperator::Or: return OpOr; - case QSOperator::RShift: return OpRShift; - case QSOperator::StrictEqual: return OpStrictEqual; - case QSOperator::StrictNotEqual: return OpStrictNotEqual; - case QSOperator::Sub: return OpSub; - case QSOperator::URShift: return OpURShift; - case QSOperator::InstanceOf: return OpInstanceof; - case QSOperator::In: return OpIn; - default: return OpInvalid; - } -} - -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) - { - subexpressions.clear(); - subexpressions.reserve(function->basicBlockCount() * 8); - - for (BasicBlock *block : function->basicBlocks()) { - if (block->isRemoved()) - continue; - clone.setBasicBlock(block); - - for (Stmt *s : block->statements()) { - visit(s); - } - } - } - -private: - template <typename Expr_> - Expr_ *cleanup(Expr_ *expr) - { - std::vector<Expr *>::iterator it = std::lower_bound(subexpressions.begin(), subexpressions.end(), expr); - if (it == subexpressions.end() || *it != expr) { - subexpressions.insert(it, expr); - IR::Expr *e = expr; - qSwap(uniqueExpr, e); - visit(expr); - qSwap(uniqueExpr, e); - return static_cast<Expr_ *>(e); - } - - // the cloned expression is unique by definition - // so we don't need to add it to `subexpressions'. - return clone(expr); - } - - void visit(Stmt *s) - { - 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); - } - } - - void visit(Expr *e) - { - 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); - } - } -}; - -void Name::initGlobal(const QString *id, quint32 line, quint32 column) -{ - this->id = id; - this->builtin = builtin_invalid; - this->global = true; - this->qmlSingleton = false; - this->freeOfSideEffects = false; - this->line = line; - this->column = column; -} - -void Name::init(const QString *id, quint32 line, quint32 column) -{ - this->id = id; - this->builtin = builtin_invalid; - this->global = false; - this->qmlSingleton = false; - this->freeOfSideEffects = false; - this->line = line; - this->column = column; -} - -void Name::init(Builtin builtin, quint32 line, quint32 column) -{ - this->id = 0; - this->builtin = builtin; - this->global = false; - this->qmlSingleton = false; - this->freeOfSideEffects = false; - this->line = line; - this->column = column; -} - -const char *builtin_to_string(Name::Builtin b) -{ - switch (b) { - case Name::builtin_invalid: - return "builtin_invalid"; - case Name::builtin_typeof: - return "builtin_typeof"; - case Name::builtin_delete: - return "builtin_delete"; - case Name::builtin_throw: - return "builtin_throw"; - case Name::builtin_rethrow: - return "builtin_rethrow"; - case Name::builtin_unwind_exception: - return "builtin_unwind_exception"; - case Name::builtin_push_catch_scope: - return "builtin_push_catch_scope"; - case IR::Name::builtin_foreach_iterator_object: - return "builtin_foreach_iterator_object"; - case IR::Name::builtin_foreach_next_property_name: - return "builtin_foreach_next_property_name"; - case IR::Name::builtin_push_with_scope: - return "builtin_push_with_scope"; - case IR::Name::builtin_pop_scope: - return "builtin_pop_scope"; - case IR::Name::builtin_declare_vars: - return "builtin_declare_vars"; - case IR::Name::builtin_define_array: - return "builtin_define_array"; - case IR::Name::builtin_define_object_literal: - return "builtin_define_object_literal"; - case IR::Name::builtin_setup_argument_object: - return "builtin_setup_argument_object"; - case IR::Name::builtin_convert_this_to_object: - return "builtin_convert_this_to_object"; - case IR::Name::builtin_qml_context: - return "builtin_qml_context"; - case IR::Name::builtin_qml_imported_scripts_object: - return "builtin_qml_imported_scripts_object"; - } - return "builtin_(###FIXME)"; -}; - -bool operator<(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW -{ - if (t1.kind < t2.kind) return true; - if (t1.kind > t2.kind) return false; - return t1.index < t2.index; -} - -Function *Module::newFunction(const QString &name, Function *outer) -{ - Function *f = new Function(this, outer, name); - functions.append(f); - if (!outer) { - if (!isQmlModule) { - Q_ASSERT(!rootFunction); - rootFunction = f; - } - } else { - outer->nestedFunctions.append(f); - } - return f; -} - -Module::~Module() -{ - qDeleteAll(functions); -} - -void Module::setFileName(const QString &name) -{ - fileName = name; -} - -Function::Function(Module *module, Function *outer, const QString &name) - : module(module) - , pool(&module->pool) - , tempCount(0) - , maxNumberOfArguments(0) - , outer(outer) - , insideWithOrCatch(0) - , hasDirectEval(false) - , usesArgumentsObject(false) - , isStrict(false) - , isNamedExpression(false) - , hasTry(false) - , hasWith(false) - , isQmlBinding(false) - , unused(0) - , line(0) - , column(0) - , _allBasicBlocks(0) - , _statementCount(0) -{ - this->name = newString(name); - _basicBlocks.reserve(8); -} - -Function::~Function() -{ - if (_allBasicBlocks) { - qDeleteAll(*_allBasicBlocks); - delete _allBasicBlocks; - } else { - qDeleteAll(_basicBlocks); - } - - pool = 0; - module = 0; -} - - -const QString *Function::newString(const QString &text) -{ - return &*strings.insert(text); -} - -BasicBlock *Function::newBasicBlock(BasicBlock *catchBlock, BasicBlockInsertMode mode) -{ - BasicBlock *block = new BasicBlock(this, catchBlock); - return mode == InsertBlock ? addBasicBlock(block) : block; -} - -BasicBlock *Function::addBasicBlock(BasicBlock *block) -{ - Q_ASSERT(block->index() < 0); - block->setIndex(_basicBlocks.size()); - _basicBlocks.append(block); - return block; -} - -void Function::removeBasicBlock(BasicBlock *block) -{ - block->markAsRemoved(); - block->in.clear(); - block->out.clear(); -} - -int Function::liveBasicBlocksCount() const -{ - int count = 0; - for (BasicBlock *bb : basicBlocks()) - if (!bb->isRemoved()) - ++count; - return count; -} - -void Function::removeSharedExpressions() -{ - RemoveSharedExpressions removeSharedExpressions; - removeSharedExpressions(this); -} - -int Function::indexOfArgument(const QStringRef &string) const -{ - for (int i = formals.size() - 1; i >= 0; --i) { - if (*formals.at(i) == string) - return i; - } - return -1; -} - -void Function::setScheduledBlocks(const QVector<BasicBlock *> &scheduled) -{ - Q_ASSERT(!_allBasicBlocks); - _allBasicBlocks = new QVector<BasicBlock *>(basicBlocks()); - _basicBlocks = scheduled; - for (int i = 0, ei = basicBlockCount(); i != ei; ++i) - basicBlock(i)->changeIndex(i); -} - -BasicBlock *Function::getOrCreateBasicBlock(int index) -{ - if (_basicBlocks.size() <= index) { - const int oldSize = _basicBlocks.size(); - _basicBlocks.resize(index + 1); - for (int i = oldSize; i <= index; ++i) { - BasicBlock *block = new BasicBlock(this, 0); - block->setIndex(i); - _basicBlocks[i] = block; - } - } - - return _basicBlocks.at(index); -} - -void Function::setStatementCount(int cnt) -{ - _statementCount = cnt; -} - -void BasicBlock::setStatements(const QVector<Stmt *> &newStatements) -{ - Q_ASSERT(!isRemoved()); - Q_ASSERT(newStatements.size() >= _statements.size()); - for (Stmt *s : qAsConst(_statements)) { - if (Phi *p = s->asPhi()) { - if (!newStatements.contains(p)) { - // phi-node was not copied over, so: - p->destroyData(); - } - } else { - break; - } - } - _statements = newStatements; -} - -CloneExpr::CloneExpr(BasicBlock *block) - : block(block), cloned(0) -{ -} - -void CloneExpr::setBasicBlock(BasicBlock *block) -{ - this->block = block; -} - -ExprList *CloneExpr::clone(ExprList *list) -{ - if (! list) - return 0; - - ExprList *clonedList = block->function->New<IR::ExprList>(); - clonedList->init(clone(list->expr), clone(list->next)); - return clonedList; -} - -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) - : out(out) - , positionSize(Stmt::InvalidId) - , currentBB(0) -{ -} - -IRPrinter::~IRPrinter() -{ -} - -void IRPrinter::print(Stmt *s) -{ - visit(s); -} - -void IRPrinter::print(const Expr &e) -{ - visit(const_cast<Expr *>(&e)); -} - -void IRPrinter::print(Expr *e) -{ - visit(e); -} - -void IRPrinter::print(Function *f) -{ - if (positionSize == Stmt::InvalidId) - positionSize = QString::number(f->statementCount()).size(); - - QString n = f->name ? *f->name : QString(); - if (n.isEmpty()) - n.sprintf("%p", f); - *out << "function " << n << '('; - - for (int i = 0; i < f->formals.size(); ++i) { - if (i != 0) - *out << ", "; - *out << *f->formals.at(i); - } - *out << ')' << endl - << '{' << endl; - - for (const QString *local : qAsConst(f->locals)) - *out << " local var " << *local << endl; - - bool needsSeperator = !f->locals.isEmpty(); - for (BasicBlock *bb : f->basicBlocks()) { - if (bb->isRemoved()) - continue; - - if (needsSeperator) - *out << endl; - else - needsSeperator = true; - print(bb); - } - *out << '}' << endl; -} - -void IRPrinter::print(BasicBlock *bb) -{ - std::swap(currentBB, bb); - printBlockStart(); - - for (Stmt *s : currentBB->statements()) { - if (!s) - continue; - - QByteArray str; - QBuffer buf(&str); - buf.open(QIODevice::WriteOnly); - QTextStream os(&buf); - QTextStream *prevOut = &os; - std::swap(out, prevOut); - addStmtNr(s); - visit(s); - if (s->location.startLine) { - out->flush(); - for (int i = 58 - str.length(); i > 0; --i) - *out << ' '; - *out << " ; line: " << s->location.startLine << ", column: " << s->location.startColumn; - } - - out->flush(); - std::swap(out, prevOut); - - *out << " " << str << endl; - } - - 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 "; - visit(s->expr); -} - -void IRPrinter::visitMove(Move *s) -{ - if (Temp *targetTemp = s->target->asTemp()) - if (!s->swap && targetTemp->type != UnknownType) - *out << typeName(targetTemp->type) << ' '; - - visit(s->target); - *out << ' '; - if (s->swap) - *out << "<=> "; - else - *out << "= "; - visit(s->source); -} - -void IRPrinter::visitJump(Jump *s) -{ - *out << "goto L" << s->target->index(); -} - -void IRPrinter::visitCJump(CJump *s) -{ - *out << "if "; - visit(s->cond); - *out << " goto L" << s->iftrue->index() - << " else goto L" << s->iffalse->index(); -} - -void IRPrinter::visitRet(Ret *s) -{ - *out << "return"; - if (s->expr) { - *out << ' '; - visit(s->expr); - } -} - -void IRPrinter::visitPhi(Phi *s) -{ - if (s->targetTemp->type != UnknownType) - *out << typeName(s->targetTemp->type) << ' '; - - visit(s->targetTemp); - *out << " = phi "; - for (int i = 0, ei = s->incoming.size(); i < ei; ++i) { - if (i > 0) - *out << ", "; - if (currentBB) - *out << 'L' << currentBB->in.at(i)->index() << ": "; - if (s->incoming[i]) - 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(); - } -} - -void IRPrinter::visitConst(Const *e) -{ - switch (e->type) { - case QV4::IR::UndefinedType: - *out << "undefined"; - break; - case QV4::IR::NullType: - *out << "null"; - break; - case QV4::IR::BoolType: - *out << (e->value ? "true" : "false"); - break; - case QV4::IR::MissingType: - *out << "missing"; - break; - default: - if (int(e->value) == 0 && int(e->value) == e->value) { - if (isNegative(e->value)) - *out << "-0"; - else - *out << "0"; - } else { - *out << QString::number(e->value, 'g', 16); - } - break; - } -} - -void IRPrinter::visitString(String *e) -{ - *out << '"' << escape(*e->value) << '"'; -} - -void IRPrinter::visitRegExp(RegExp *e) -{ - char f[3]; - int i = 0; - if (e->flags & RegExp::RegExp_Global) - f[i++] = 'g'; - if (e->flags & RegExp::RegExp_IgnoreCase) - f[i++] = 'i'; - if (e->flags & RegExp::RegExp_Multiline) - f[i++] = 'm'; - f[i] = 0; - - *out << '/' << *e->value << '/' << f; -} - -void IRPrinter::visitName(Name *e) -{ - if (e->id) { - if (*e->id != QLatin1String("this")) - *out << '.'; - *out << *e->id; - } else { - *out << builtin_to_string(e->builtin); - } -} - -void IRPrinter::visitTemp(Temp *e) -{ - switch (e->kind) { - case Temp::VirtualRegister: *out << '%' << e->index; break; - case Temp::PhysicalRegister: *out << (e->type == DoubleType ? "fp" : "r") - << e->index; break; - case Temp::StackSlot: *out << '&' << e->index; break; - default: *out << "INVALID"; - } -} - -void IRPrinter::visitArgLocal(ArgLocal *e) -{ - switch (e->kind) { - case ArgLocal::Formal: *out << '#' << e->index; break; - case ArgLocal::ScopedFormal: *out << '#' << e->index - << '@' << e->scope; break; - case ArgLocal::Local: *out << '$' << e->index; break; - case ArgLocal::ScopedLocal: *out << '$' << e->index - << '@' << e->scope; break; - default: *out << "INVALID"; - } -} - -void IRPrinter::visitClosure(Closure *e) -{ - QString name = e->functionName ? *e->functionName : QString(); - if (name.isEmpty()) - name.sprintf("%x", e->value); - *out << "closure " << name; -} - -void IRPrinter::visitConvert(Convert *e) -{ - *out << "convert " << typeName(e->expr->type) << " to " << typeName(e->type) << ' '; - visit(e->expr); -} - -void IRPrinter::visitUnop(Unop *e) -{ - *out << opname(e->op) << ' '; - visit(e->expr); -} - -void IRPrinter::visitBinop(Binop *e) -{ - *out << opname(e->op) << ' '; - visit(e->left); - *out << ", "; - visit(e->right); -} - -void IRPrinter::visitCall(Call *e) -{ - *out << "call "; - visit(e->base); - *out << '('; - for (ExprList *it = e->args; it; it = it->next) { - if (it != e->args) - *out << ", "; - visit(it->expr); - } - *out << ')'; -} - -void IRPrinter::visitNew(New *e) -{ - *out << "new "; - visit(e->base); - *out << '('; - for (ExprList *it = e->args; it; it = it->next) { - if (it != e->args) - *out << ", "; - visit(it->expr); - } - *out << ')'; -} - -void IRPrinter::visitSubscript(Subscript *e) -{ - visit(e->base); - *out << '['; - visit(e->index); - *out << ']'; -} - -void IRPrinter::visitMember(Member *e) -{ - if (e->kind != Member::MemberOfEnum && e->kind != Member::MemberOfIdObjectsArray - && e->attachedPropertiesId != 0 && !e->base->asTemp()) - *out << "[[attached property from " << e->attachedPropertiesId << "]]"; - else - visit(e->base); - *out << '.' << *e->name; -#ifndef V4_BOOTSTRAP - if (e->property) - *out << " (meta-property " << e->property->coreIndex() - << " <" << QMetaType::typeName(e->property->propType()) - << ">)"; - else if (e->kind == Member::MemberOfIdObjectsArray) - *out << "(id object " << e->idIndex << ")"; -#endif -} - -QString IRPrinter::escape(const QString &s) -{ - QString r; - for (int i = 0; i < s.length(); ++i) { - const QChar ch = s.at(i); - if (ch == QLatin1Char('\n')) - r += QLatin1String("\\n"); - else if (ch == QLatin1Char('\r')) - r += QLatin1String("\\r"); - else if (ch == QLatin1Char('\\')) - r += QLatin1String("\\\\"); - else if (ch == QLatin1Char('"')) - r += QLatin1String("\\\""); - else if (ch == QLatin1Char('\'')) - r += QLatin1String("\\'"); - else - r += ch; - } - return r; -} - -void IRPrinter::addStmtNr(Stmt *s) -{ - if (s->id() >= 0) - addJustifiedNr(s->id()); -} - -void IRPrinter::addJustifiedNr(int pos) -{ - if (positionSize == Stmt::InvalidId) { - *out << pos << ": "; - } else { - QString posStr; - if (pos != Stmt::InvalidId) - posStr = QString::number(pos); - *out << posStr.rightJustified(positionSize); - if (pos == Stmt::InvalidId) - *out << " "; - else - *out << ": "; - } -} - -void IRPrinter::printBlockStart() -{ - if (currentBB->isRemoved()) { - *out << "(block has been removed)"; - return; - } - - QByteArray str; - str.append('L'); - str.append(QByteArray::number(currentBB->index())); - str.append(':'); - if (currentBB->catchBlock) { - str.append(" (exception handler L"); - str.append(QByteArray::number(currentBB->catchBlock->index())); - str.append(')'); - } - for (int i = 66 - str.length(); i; --i) - str.append(' '); - *out << str; - - *out << "; predecessors:"; - for (BasicBlock *in : qAsConst(currentBB->in)) - *out << " L" << in->index(); - if (currentBB->in.isEmpty()) - *out << " none"; - if (BasicBlock *container = currentBB->containingGroup()) - *out << ", container: L" << container->index(); - if (currentBB->isGroupStart()) - *out << ", loop_header: yes"; - *out << endl; -} - -} // end of namespace IR -} // end of namespace QV4 - -QT_END_NAMESPACE diff --git a/src/qml/compiler/qv4jsir_p.h b/src/qml/compiler/qv4jsir_p.h deleted file mode 100644 index b534eaa54f..0000000000 --- a/src/qml/compiler/qv4jsir_p.h +++ /dev/null @@ -1,1792 +0,0 @@ -/**************************************************************************** -** -** 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 QV4JSIR_P_H -#define QV4JSIR_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "private/qv4global_p.h" -#include <private/qqmljsmemorypool_p.h> -#include <private/qqmljsastfwd_p.h> -#include <private/qflagpointer_p.h> - -#include <QtCore/private/qnumeric_p.h> -#include <QtCore/QVector> -#include <QtCore/QString> -#include <QtCore/QBitArray> -#include <QtCore/qurl.h> -#include <QtCore/QVarLengthArray> -#include <QtCore/QDateTime> -#include <qglobal.h> - -#if defined(CONST) && defined(Q_OS_WIN) -# define QT_POP_CONST -# pragma push_macro("CONST") -# undef CONST // CONST conflicts with our own identifier -#endif - -QT_BEGIN_NAMESPACE - -class QTextStream; -class QQmlType; -class QQmlPropertyData; -class QQmlPropertyCache; -class QQmlEnginePrivate; - -namespace QV4 { - -inline bool isNegative(double d) -{ - uchar *dch = (uchar *)&d; - if (QSysInfo::ByteOrder == QSysInfo::BigEndian) - return (dch[0] & 0x80); - else - return (dch[7] & 0x80); - -} - -namespace IR { - -struct BasicBlock; -struct Function; -struct Module; - -struct Stmt; -struct Expr; - -// expressions -struct Const; -struct String; -struct RegExp; -struct Name; -struct Temp; -struct ArgLocal; -struct Closure; -struct Convert; -struct Unop; -struct Binop; -struct Call; -struct New; -struct Subscript; -struct Member; - -// statements -struct Exp; -struct Move; -struct Jump; -struct CJump; -struct Ret; -struct Phi; - -template<class T, int Prealloc> -class VarLengthArray: public QVarLengthArray<T, Prealloc> -{ -public: - bool removeOne(const T &element) - { - for (int i = 0; i < this->size(); ++i) { - if (this->at(i) == element) { - this->remove(i); - return true; - } - } - - return false; - } -}; - -// Flag pointer: -// * The first flag indicates whether the meta object is final. -// If final, then none of its properties themselves need to -// be final when considering for lookups in QML. -// * The second flag indicates whether enums should be included -// in the lookup of properties or not. The default is false. -typedef QFlagPointer<QQmlPropertyCache> IRMetaObject; - -enum AluOp { - OpInvalid = 0, - - OpIfTrue, - OpNot, - OpUMinus, - OpUPlus, - OpCompl, - OpIncrement, - OpDecrement, - - OpBitAnd, - OpBitOr, - OpBitXor, - - OpAdd, - OpSub, - OpMul, - OpDiv, - OpMod, - - OpLShift, - OpRShift, - OpURShift, - - OpGt, - OpLt, - OpGe, - OpLe, - OpEqual, - OpNotEqual, - OpStrictEqual, - OpStrictNotEqual, - - OpInstanceof, - OpIn, - - OpAnd, - OpOr, - - LastAluOp = OpOr -}; -AluOp binaryOperator(int op); -const char *opname(IR::AluOp op); - -enum Type : quint16 { - UnknownType = 0, - - MissingType = 1 << 0, - UndefinedType = 1 << 1, - NullType = 1 << 2, - BoolType = 1 << 3, - - SInt32Type = 1 << 4, - UInt32Type = 1 << 5, - DoubleType = 1 << 6, - NumberType = SInt32Type | UInt32Type | DoubleType, - - StringType = 1 << 7, - QObjectType = 1 << 8, - VarType = 1 << 9 -}; - -inline bool strictlyEqualTypes(Type t1, Type t2) -{ - return t1 == t2 || ((t1 & NumberType) && (t2 & NumberType)); -} - -QString typeName(Type t); - -struct MemberExpressionResolver; - -struct DiscoveredType { - int type; - MemberExpressionResolver *memberResolver; - - DiscoveredType() : type(UnknownType), memberResolver(0) {} - DiscoveredType(Type t) : type(t), memberResolver(0) { Q_ASSERT(type != QObjectType); } - explicit DiscoveredType(int t) : type(t), memberResolver(0) { Q_ASSERT(type != QObjectType); } - explicit DiscoveredType(MemberExpressionResolver *memberResolver) - : type(QObjectType) - , memberResolver(memberResolver) - { Q_ASSERT(memberResolver); } - - bool test(Type t) const { return type & t; } - bool isNumber() const { return (type & NumberType) && !(type & ~NumberType); } - - bool operator!=(Type other) const { return type != other; } - bool operator==(Type other) const { return type == other; } - bool operator==(const DiscoveredType &other) const { return type == other.type; } - bool operator!=(const DiscoveredType &other) const { return type != other.type; } -}; - -struct MemberExpressionResolver -{ - typedef DiscoveredType (*ResolveFunction)(QQmlEnginePrivate *engine, - const MemberExpressionResolver *resolver, - Member *member); - - MemberExpressionResolver() - : resolveMember(0), data(0), extraData(0), owner(nullptr), flags(0) {} - - bool isValid() const { return !!resolveMember; } - void clear() { *this = MemberExpressionResolver(); } - - ResolveFunction resolveMember; - void *data; // Could be pointer to meta object, importNameSpace, etc. - depends on resolveMember implementation - void *extraData; // Could be QQmlTypeNameCache - Function *owner; - unsigned int flags; -}; - -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); - } - - 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(); - String *asString(); - RegExp *asRegExp(); - Name *asName(); - Temp *asTemp(); - ArgLocal *asArgLocal(); - Closure *asClosure(); - Convert *asConvert(); - Unop *asUnop(); - Binop *asBinop(); - Call *asCall(); - New *asNew(); - Subscript *asSubscript(); - Member *asMember(); -}; - -#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; - - ExprList(): expr(0), next(0) {} - - void init(Expr *expr, ExprList *next = 0) - { - this->expr = expr; - this->next = next; - } -}; - -struct Const: Expr { - double value; - - Const(): Expr(ConstExpr) {} - - void init(Type type, double value) - { - this->type = type; - this->value = value; - } - - 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; - } - - static bool classof(const Expr *c) { return c->exprKind == StringExpr; } -}; - -struct RegExp: Expr { - // needs to be compatible with the flags in the lexer, and in RegExpObject - enum Flags { - RegExp_Global = 0x01, - RegExp_IgnoreCase = 0x02, - RegExp_Multiline = 0x04 - }; - - const QString *value; - int flags; - - RegExp(): Expr(RegExpExpr) {} - - void init(const QString *value, int flags) - { - this->value = value; - this->flags = flags; - } - - static bool classof(const Expr *c) { return c->exprKind == RegExpExpr; } -}; - -struct Name: Expr { - enum Builtin { - builtin_invalid, - builtin_typeof, - builtin_delete, - builtin_throw, - builtin_rethrow, - builtin_unwind_exception, - builtin_push_catch_scope, - builtin_foreach_iterator_object, - builtin_foreach_next_property_name, - builtin_push_with_scope, - builtin_pop_scope, - builtin_declare_vars, - builtin_define_array, - builtin_define_object_literal, - builtin_setup_argument_object, - builtin_convert_this_to_object, - builtin_qml_context, - builtin_qml_imported_scripts_object - }; - - const QString *id; - Builtin builtin; - bool global : 1; - bool qmlSingleton : 1; - bool freeOfSideEffects : 1; - 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); - - static bool classof(const Expr *c) { return c->exprKind == NameExpr; } -}; - -struct Q_AUTOTEST_EXPORT Temp: Expr { - enum Kind { - Invalid = 0, - VirtualRegister, - PhysicalRegister, - StackSlot - }; - - unsigned index : 28; - unsigned isReadOnly : 1; - unsigned kind : 3; - - // Used when temp is used as base in member expression - MemberExpressionResolver *memberResolver; - - Temp() - : Expr(TempExpr) - , index((1 << 28) - 1) - , isReadOnly(0) - , kind(Invalid) - , memberResolver(0) - {} - - Temp(Type type, Kind kind, unsigned index) - : Expr(TempExpr) - , index(index) - , isReadOnly(0) - , kind(kind) - , memberResolver(0) - { - this->type = type; - } - - void init(unsigned kind, unsigned index) - { - this->index = index; - this->isReadOnly = false; - this->kind = kind; - } - - bool isInvalid() const { return kind == Invalid; } - - static bool classof(const Expr *c) { return c->exprKind == TempExpr; } -}; - -inline bool operator==(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW -{ return t1.index == t2.index && t1.kind == t2.kind && t1.type == t2.type; } - -inline bool operator!=(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW -{ return !(t1 == t2); } - -inline uint qHash(const Temp &t, uint seed = 0) Q_DECL_NOTHROW -{ return t.index ^ t.kind ^ seed; } - -bool operator<(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW; - -struct Q_AUTOTEST_EXPORT ArgLocal: Expr { - enum Kind { - Formal = 0, - ScopedFormal, - Local, - ScopedLocal - }; - - unsigned index; - unsigned scope : 29; // how many scopes outside the current one? - unsigned kind : 2; - unsigned isArgumentsOrEval : 1; - - void init(unsigned kind, unsigned index, unsigned scope) - { - Q_ASSERT((kind == ScopedLocal && scope != 0) || - (kind == ScopedFormal && scope != 0) || - (scope == 0)); - - this->kind = kind; - this->index = index; - this->scope = scope; - this->isArgumentsOrEval = false; - } - - 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; - } - - 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; - } - - static bool classof(const Expr *c) { return c->exprKind == ConvertExpr; } -}; - -struct Unop: Expr { - Expr *expr; - AluOp op; - - Unop(): Expr(UnopExpr) {} - - void init(AluOp op, Expr *expr) - { - this->op = op; - this->expr = expr; - } - - static bool classof(const Expr *c) { return c->exprKind == UnopExpr; } -}; - -struct Binop: Expr { - Expr *left; // Temp or Const - Expr *right; // Temp or Const - AluOp op; - - Binop(): Expr(BinopExpr) {} - - void init(AluOp op, Expr *left, Expr *right) - { - this->op = op; - this->left = left; - this->right = right; - } - - 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; - this->args = args; - } - - Expr *onlyArgument() const { - if (args && ! args->next) - return args->expr; - return 0; - } - - 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; - this->args = args; - } - - Expr *onlyArgument() const { - if (args && ! args->next) - return args->expr; - return 0; - } - - 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; - } - - static bool classof(const Expr *c) { return c->exprKind == SubscriptExpr; } -}; - -struct Member: Expr { - // Used for property dependency tracking - enum MemberKind { - UnspecifiedMember, - MemberOfEnum, - MemberOfQmlScopeObject, - MemberOfQmlContextObject, - MemberOfIdObjectsArray, - MemberOfSingletonObject, - }; - - Expr *base; - const QString *name; - QQmlPropertyData *property; - union { // depending on kind - int attachedPropertiesId; - int enumValue; - int idIndex; - }; - uchar freeOfSideEffects : 1; - - // This is set for example for for QObject properties. All sorts of extra behavior - // is defined when writing to them, for example resettable properties are reset - // when writing undefined to them, and an exception is thrown when they're missing - // a reset function. And then there's also Qt.binding(). - uchar inhibitTypeConversionOnWrite: 1; - - uchar kind: 3; // MemberKind - - Member(): Expr(MemberExpr) {} - - void setEnumValue(int value) { - kind = MemberOfEnum; - enumValue = value; - } - - void setAttachedPropertiesId(int id) { - Q_ASSERT(kind != MemberOfEnum && kind != MemberOfIdObjectsArray); - attachedPropertiesId = id; - } - - void init(Expr *base, const QString *name, QQmlPropertyData *property = 0, uchar kind = UnspecifiedMember, int index = 0) - { - this->base = base; - this->name = name; - this->property = property; - this->idIndex = index; - this->freeOfSideEffects = false; - this->inhibitTypeConversionOnWrite = property != 0; - this->kind = kind; - } - - 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, StmtKind stmtKind): _id(id), stmtKind(stmtKind) {} - - Stmt *asTerminator(); - - Exp *asExp(); - Move *asMove(); - Jump *asJump(); - CJump *asCJump(); - Ret *asRet(); - Phi *asPhi(); - - int id() const { return _id; } - -private: // For memory management in BasicBlock - friend struct 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, ExpStmt) {} - - void init(Expr *expr) - { - this->expr = expr; - } - - static bool classof(const Stmt *c) { return c->stmtKind == ExpStmt; } -}; - -struct Move: Stmt { - Expr *target; // LHS - Temp, Name, Member or Subscript - Expr *source; - bool swap; - - Move(int id): Stmt(id, MoveStmt) {} - - void init(Expr *target, Expr *source) - { - this->target = target; - this->source = source; - this->swap = false; - } - - static bool classof(const Stmt *c) { return c->stmtKind == MoveStmt; } -}; - -struct Jump: Stmt { - BasicBlock *target; - - Jump(int id): Stmt(id, JumpStmt) {} - - void init(BasicBlock *target) - { - this->target = target; - } - - static bool classof(const Stmt *c) { return c->stmtKind == JumpStmt; } -}; - -struct CJump: Stmt { - Expr *cond; // Temp, Binop - BasicBlock *iftrue; - BasicBlock *iffalse; - BasicBlock *parent; - - CJump(int id): Stmt(id, CJumpStmt) {} - - void init(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse, BasicBlock *parent) - { - this->cond = cond; - this->iftrue = iftrue; - this->iffalse = iffalse; - this->parent = parent; - } - - static bool classof(const Stmt *c) { return c->stmtKind == CJumpStmt; } -}; - -struct Ret: Stmt { - Expr *expr; - - Ret(int id): Stmt(id, RetStmt) {} - - void init(Expr *expr) - { - this->expr = expr; - } - - 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 -// subsequent to eachother, and the first phi node should be the first statement in the basic-block. -// A number of loops rely on this behavior, so they don't need to walk through the whole list -// of instructions in a basic-block (e.g. the calls to destroyData in BasicBlock::~BasicBlock). -struct Phi: Stmt { - Temp *targetTemp; - VarLengthArray<Expr *, 4> incoming; - - Phi(int id): Stmt(id, PhiStmt) {} - - 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; - QDateTime sourceTimeStamp; - bool isQmlModule; // implies rootFunction is always 0 - uint unitFlags; // flags merged into CompiledData::Unit::flags - QString targetABI; // fallback to QSysInfo::buildAbi() if empty -#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) - , isQmlModule(false) - , unitFlags(0) -#ifndef QT_NO_QML_DEBUGGER - , debugMode(debugMode) - {} -#else - { Q_UNUSED(debugMode); } -#endif - ~Module(); - - void setFileName(const QString &name); -}; - -struct BasicBlock { -private: - Q_DISABLE_COPY(BasicBlock) - -public: - typedef VarLengthArray<BasicBlock *, 4> IncomingEdges; - typedef VarLengthArray<BasicBlock *, 2> OutgoingEdges; - - Function *function; - BasicBlock *catchBlock; - IncomingEdges in; - OutgoingEdges out; - QQmlJS::AST::SourceLocation nextLocation; - - BasicBlock(Function *function, BasicBlock *catcher) - : function(function) - , catchBlock(catcher) - , _containingGroup(0) - , _index(-1) - , _isExceptionHandler(false) - , _groupStart(false) - , _isRemoved(false) - {} - - ~BasicBlock() - { - for (Stmt *s : qAsConst(_statements)) { - if (Phi *p = s->asPhi()) { - p->destroyData(); - } else { - break; - } - } - } - - const QVector<Stmt *> &statements() const - { - Q_ASSERT(!isRemoved()); - return _statements; - } - - int statementCount() const - { - Q_ASSERT(!isRemoved()); - return _statements.size(); - } - - void setStatements(const QVector<Stmt *> &newStatements); - - template <typename Instr> inline Instr i(Instr i) - { - Q_ASSERT(!isRemoved()); - appendStatement(i); - return i; - } - - void appendStatement(Stmt *statement) - { - Q_ASSERT(!isRemoved()); - if (nextLocation.startLine) - statement->location = nextLocation; - _statements.append(statement); - } - - void prependStatement(Stmt *stmt) - { - Q_ASSERT(!isRemoved()); - _statements.prepend(stmt); - } - - void prependStatements(const QVector<Stmt *> &stmts) - { - Q_ASSERT(!isRemoved()); - QVector<Stmt *> newStmts = stmts; - newStmts += _statements; - _statements = newStmts; - } - - void insertStatementBefore(Stmt *before, Stmt *newStmt) - { - int idx = _statements.indexOf(before); - Q_ASSERT(idx >= 0); - _statements.insert(idx, newStmt); - } - - void insertStatementBefore(int index, Stmt *newStmt) - { - Q_ASSERT(index >= 0); - _statements.insert(index, newStmt); - } - - void insertStatementBeforeTerminator(Stmt *stmt) - { - Q_ASSERT(!isRemoved()); - _statements.insert(_statements.size() - 1, stmt); - } - - void replaceStatement(int index, Stmt *newStmt) - { - Q_ASSERT(!isRemoved()); - if (Phi *p = _statements[index]->asPhi()) { - p->destroyData(); - } - _statements[index] = newStmt; - } - - void removeStatement(Stmt *stmt) - { - Q_ASSERT(!isRemoved()); - if (Phi *p = stmt->asPhi()) { - p->destroyData(); - } - _statements.remove(_statements.indexOf(stmt)); - } - - void removeStatement(int idx) - { - Q_ASSERT(!isRemoved()); - if (Phi *p = _statements[idx]->asPhi()) { - p->destroyData(); - } - _statements.remove(idx); - } - - inline bool isEmpty() const { - Q_ASSERT(!isRemoved()); - return _statements.isEmpty(); - } - - inline Stmt *terminator() const { - Q_ASSERT(!isRemoved()); - if (! _statements.isEmpty() && _statements.last()->asTerminator() != 0) - return _statements.last(); - return 0; - } - - inline bool isTerminated() const { - Q_ASSERT(!isRemoved()); - if (terminator() != 0) - return true; - return false; - } - - unsigned newTemp(); - - Temp *TEMP(unsigned kind); - ArgLocal *ARG(unsigned index, unsigned scope); - ArgLocal *LOCAL(unsigned index, unsigned scope); - - Expr *CONST(Type type, double value); - Expr *STRING(const QString *value); - Expr *REGEXP(const QString *value, int flags); - - Name *NAME(const QString &id, quint32 line, quint32 column); - Name *NAME(Name::Builtin builtin, quint32 line, quint32 column); - - Name *GLOBALNAME(const QString &id, quint32 line, quint32 column); - - Closure *CLOSURE(int functionInModule); - - Expr *CONVERT(Expr *expr, Type type); - Expr *UNOP(AluOp op, Expr *expr); - Expr *BINOP(AluOp op, Expr *left, Expr *right); - Expr *CALL(Expr *base, ExprList *args = 0); - Expr *NEW(Expr *base, ExprList *args = 0); - Expr *SUBSCRIPT(Expr *base, Expr *index); - Expr *MEMBER(Expr *base, const QString *name, QQmlPropertyData *property = 0, uchar kind = Member::UnspecifiedMember, int attachedPropertiesIdOrEnumValue = 0); - - Stmt *EXP(Expr *expr); - - Stmt *MOVE(Expr *target, Expr *source); - - Stmt *JUMP(BasicBlock *target); - Stmt *CJUMP(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse); - Stmt *RET(Expr *expr); - - BasicBlock *containingGroup() const - { - Q_ASSERT(!isRemoved()); - return _containingGroup; - } - - void setContainingGroup(BasicBlock *loopHeader) - { - Q_ASSERT(!isRemoved()); - _containingGroup = loopHeader; - } - - bool isGroupStart() const - { - Q_ASSERT(!isRemoved()); - return _groupStart; - } - - void markAsGroupStart(bool mark = true) - { - Q_ASSERT(!isRemoved()); - _groupStart = mark; - } - - // Returns the index of the basic-block. - // See Function for the full description. - int index() const - { - Q_ASSERT(!isRemoved()); - return _index; - } - - bool isExceptionHandler() const - { return _isExceptionHandler; } - - void setExceptionHandler(bool onoff) - { _isExceptionHandler = onoff; } - - bool isRemoved() const - { return _isRemoved; } - -private: // For Function's eyes only. - friend struct Function; - void setIndex(int index) - { - Q_ASSERT(_index < 0); - changeIndex(index); - } - - void changeIndex(int index) - { - Q_ASSERT(index >= 0); - _index = index; - } - - void markAsRemoved() - { - _isRemoved = true; - _index = -1; - } - -private: - QVector<Stmt *> _statements; - BasicBlock *_containingGroup; - int _index; - unsigned _isExceptionHandler : 1; - unsigned _groupStart : 1; - 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 -{ - quint32 _key; - quint32 _value; - - KeyValuePair(): _key(0), _value(0) {} - KeyValuePair(quint32 key, quint32 value): _key(key), _value(value) {} - - quint32 key() const { return _key; } - quint32 value() const { return _value; } -}; - -class PropertyDependencyMap: public QVarLengthArray<KeyValuePair, 8> -{ -public: - void insert(quint32 key, quint32 value) - { - for (auto it = begin(), eit = end(); it != eit; ++it) { - if (it->_key == key) { - it->_value = value; - return; - } - } - append(KeyValuePair(key, value)); - } -}; - -// The Function owns (manages), among things, a list of basic-blocks. All the blocks have an index, -// which corresponds to the index in the entry/index in the vector in which they are stored. This -// means that algorithms/classes can also store any information about a basic block in an array, -// where the index corresponds to the index of the basic block, which can then be used to query -// the function for a pointer to a basic block. This also means that basic-blocks cannot be removed -// or renumbered. -// -// Note that currently there is one exception: after optimization and block scheduling, the -// method setScheduledBlocks can be called once, to register a newly ordered list. For debugging -// purposes, these blocks are not immediately renumbered, so renumberBasicBlocks should be called -// immediately after changing the order. That will restore the property of having a corresponding -// block-index and block-position-in-basicBlocks-vector. -// -// In order for optimization/transformation passes to skip uninteresting basic blocks that will be -// removed, the block can be marked as such. After doing so, any access will result in a failing -// assertion. -struct Function { - Module *module; - QQmlJS::MemoryPool *pool; - const QString *name; - int tempCount; - int maxNumberOfArguments; - QSet<QString> strings; - QList<const QString *> formals; - QList<const QString *> locals; - QVector<Function *> nestedFunctions; - Function *outer; - - int insideWithOrCatch; - - uint hasDirectEval: 1; - uint usesArgumentsObject : 1; - uint usesThis : 1; - uint isStrict: 1; - uint isNamedExpression : 1; - uint hasTry: 1; - uint hasWith: 1; - uint isQmlBinding: 1; - uint unused : 24; - - // Location of declaration in source code (0 if not specified) - uint line; - uint column; - - // Qml extension: - SmallSet<int> idObjectDependencies; - PropertyDependencyMap contextObjectPropertyDependencies; - PropertyDependencyMap scopeObjectPropertyDependencies; - - template <typename T> T *New() { return new (pool->allocate(sizeof(T))) T(); } - template <typename T> T *NewStmt() { - return new (pool->allocate(sizeof(T))) T(getNewStatementId()); - } - - Function(Module *module, Function *outer, const QString &name); - ~Function(); - - enum BasicBlockInsertMode { - InsertBlock, - DontInsertBlock - }; - - BasicBlock *newBasicBlock(BasicBlock *catchBlock, BasicBlockInsertMode mode = InsertBlock); - const QString *newString(const QString &text); - - void RECEIVE(const QString &name) { formals.append(newString(name)); } - void LOCAL(const QString &name) { locals.append(newString(name)); } - - BasicBlock *addBasicBlock(BasicBlock *block); - void removeBasicBlock(BasicBlock *block); - - const QVector<BasicBlock *> &basicBlocks() const - { return _basicBlocks; } - - BasicBlock *basicBlock(int idx) const - { return _basicBlocks.at(idx); } - - int basicBlockCount() const - { return _basicBlocks.size(); } - - int liveBasicBlocksCount() const; - - void removeSharedExpressions(); - - int indexOfArgument(const QStringRef &string) const; - - bool variablesCanEscape() const - { return hasDirectEval || !nestedFunctions.isEmpty() || module->debugMode; } - - void setScheduledBlocks(const QVector<BasicBlock *> &scheduled); - - int getNewStatementId() { return _statementCount++; } - int statementCount() const { return _statementCount; } - - bool canUseSimpleCall() const { - return nestedFunctions.isEmpty() && - locals.isEmpty() && formals.size() <= QV4::Global::ReservedArgumentCount && - !hasTry && !hasWith && !isNamedExpression && !usesArgumentsObject && !hasDirectEval; - } - - bool argLocalRequiresWriteBarrier(ArgLocal *al) const { - uint scope = al->scope; - const IR::Function *f = this; - while (scope) { - f = f->outer; - --scope; - } - return !f->canUseSimpleCall(); - } - int localsCountForScope(ArgLocal *al) const { - uint scope = al->scope; - const IR::Function *f = this; - while (scope) { - f = f->outer; - --scope; - } - return f->locals.size(); - } - -private: - BasicBlock *getOrCreateBasicBlock(int index); - void setStatementCount(int cnt); - -private: - QVector<BasicBlock *> _basicBlocks; - QVector<BasicBlock *> *_allBasicBlocks; - int _statementCount; -}; - -class CloneExpr -{ -public: - explicit CloneExpr(IR::BasicBlock *block = 0); - - void setBasicBlock(IR::BasicBlock *block); - - template <typename ExprSubclass> - ExprSubclass *operator()(ExprSubclass *expr) - { - return clone(expr); - } - - template <typename ExprSubclass> - ExprSubclass *clone(ExprSubclass *expr) - { - Expr *c = expr; - qSwap(cloned, c); - visit(expr); - qSwap(cloned, c); - return static_cast<ExprSubclass *>(c); - } - - static Const *cloneConst(Const *c, Function *f) - { - Const *newConst = f->New<Const>(); - newConst->init(c->type, c->value); - return newConst; - } - - static Name *cloneName(Name *n, Function *f) - { - Name *newName = f->New<Name>(); - newName->type = n->type; - newName->id = n->id; - newName->builtin = n->builtin; - newName->global = n->global; - newName->qmlSingleton = n->qmlSingleton; - newName->freeOfSideEffects = n->freeOfSideEffects; - newName->line = n->line; - newName->column = n->column; - return newName; - } - - static Temp *cloneTemp(Temp *t, Function *f) - { - Temp *newTemp = f->New<Temp>(); - newTemp->init(t->kind, t->index); - newTemp->type = t->type; - newTemp->memberResolver = t->memberResolver; - return newTemp; - } - - static ArgLocal *cloneArgLocal(ArgLocal *argLocal, Function *f) - { - ArgLocal *newArgLocal = f->New<ArgLocal>(); - newArgLocal->init(argLocal->kind, argLocal->index, argLocal->scope); - newArgLocal->type = argLocal->type; - newArgLocal->isArgumentsOrEval = argLocal->isArgumentsOrEval; - return newArgLocal; - } - -private: - IR::ExprList *clone(IR::ExprList *list); - - void visit(Expr *e); - -protected: - IR::BasicBlock *block; - -private: - IR::Expr *cloned; -}; - -class Q_AUTOTEST_EXPORT IRPrinter -{ -public: - IRPrinter(QTextStream *out); - virtual ~IRPrinter(); - - void print(Stmt *s); - void print(Expr *e); - void print(const Expr &e); - - 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); - virtual void visitCJump(CJump *s); - 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); - virtual void visitName(Name *e); - virtual void visitTemp(Temp *e); - virtual void visitArgLocal(ArgLocal *e); - virtual void visitClosure(Closure *e); - virtual void visitConvert(Convert *e); - virtual void visitUnop(Unop *e); - virtual void visitBinop(Binop *e); - virtual void visitCall(Call *e); - virtual void visitNew(New *e); - virtual void visitSubscript(Subscript *e); - virtual void visitMember(Member *e); - - static QString escape(const QString &s); - -protected: - virtual void addStmtNr(Stmt *s); - void addJustifiedNr(int pos); - void printBlockStart(); - -protected: - QTextStream *out; - int positionSize; - BasicBlock *currentBB; -}; - -inline unsigned BasicBlock::newTemp() -{ - Q_ASSERT(!isRemoved()); - return function->tempCount++; -} - -inline Temp *BasicBlock::TEMP(unsigned index) -{ - Q_ASSERT(!isRemoved()); - Temp *e = function->New<Temp>(); - e->init(Temp::VirtualRegister, index); - return e; -} - -inline ArgLocal *BasicBlock::ARG(unsigned index, unsigned scope) -{ - Q_ASSERT(!isRemoved()); - ArgLocal *e = function->New<ArgLocal>(); - e->init(scope ? ArgLocal::ScopedFormal : ArgLocal::Formal, index, scope); - return e; -} - -inline ArgLocal *BasicBlock::LOCAL(unsigned index, unsigned scope) -{ - Q_ASSERT(!isRemoved()); - ArgLocal *e = function->New<ArgLocal>(); - e->init(scope ? ArgLocal::ScopedLocal : ArgLocal::Local, index, scope); - return e; -} - -inline Expr *BasicBlock::CONST(Type type, double value) -{ - Q_ASSERT(!isRemoved()); - Const *e = function->New<Const>(); - if (type == NumberType) { - int ival = (int)value; - // +0 != -0, so we need to convert to double when negating 0 - if (ival == value && !(value == 0 && isNegative(value))) - type = SInt32Type; - else - type = DoubleType; - } else if (type == NullType) { - value = 0; - } else if (type == UndefinedType) { - value = qt_qnan(); - } - - e->init(type, value); - return e; -} - -inline Expr *BasicBlock::STRING(const QString *value) -{ - Q_ASSERT(!isRemoved()); - String *e = function->New<String>(); - e->init(value); - return e; -} - -inline Expr *BasicBlock::REGEXP(const QString *value, int flags) -{ - Q_ASSERT(!isRemoved()); - RegExp *e = function->New<RegExp>(); - e->init(value, flags); - return e; -} - -inline Name *BasicBlock::NAME(const QString &id, quint32 line, quint32 column) -{ - Q_ASSERT(!isRemoved()); - Name *e = function->New<Name>(); - e->init(function->newString(id), line, column); - return e; -} - -inline Name *BasicBlock::GLOBALNAME(const QString &id, quint32 line, quint32 column) -{ - Q_ASSERT(!isRemoved()); - Name *e = function->New<Name>(); - e->initGlobal(function->newString(id), line, column); - return e; -} - - -inline Name *BasicBlock::NAME(Name::Builtin builtin, quint32 line, quint32 column) -{ - Q_ASSERT(!isRemoved()); - Name *e = function->New<Name>(); - e->init(builtin, line, column); - return e; -} - -inline Closure *BasicBlock::CLOSURE(int functionInModule) -{ - Q_ASSERT(!isRemoved()); - Closure *clos = function->New<Closure>(); - clos->init(functionInModule, function->module->functions.at(functionInModule)->name); - return clos; -} - -inline Expr *BasicBlock::CONVERT(Expr *expr, Type type) -{ - Q_ASSERT(!isRemoved()); - Convert *e = function->New<Convert>(); - e->init(expr, type); - return e; -} - -inline Expr *BasicBlock::UNOP(AluOp op, Expr *expr) -{ - Q_ASSERT(!isRemoved()); - Unop *e = function->New<Unop>(); - e->init(op, expr); - return e; -} - -inline Expr *BasicBlock::BINOP(AluOp op, Expr *left, Expr *right) -{ - Q_ASSERT(!isRemoved()); - Binop *e = function->New<Binop>(); - e->init(op, left, right); - return e; -} - -inline Expr *BasicBlock::CALL(Expr *base, ExprList *args) -{ - Q_ASSERT(!isRemoved()); - Call *e = function->New<Call>(); - e->init(base, args); - int argc = 0; - for (ExprList *it = args; it; it = it->next) - ++argc; - function->maxNumberOfArguments = qMax(function->maxNumberOfArguments, argc); - return e; -} - -inline Expr *BasicBlock::NEW(Expr *base, ExprList *args) -{ - Q_ASSERT(!isRemoved()); - New *e = function->New<New>(); - e->init(base, args); - return e; -} - -inline Expr *BasicBlock::SUBSCRIPT(Expr *base, Expr *index) -{ - Q_ASSERT(!isRemoved()); - Subscript *e = function->New<Subscript>(); - e->init(base, index); - return e; -} - -inline Expr *BasicBlock::MEMBER(Expr *base, const QString *name, QQmlPropertyData *property, uchar kind, int attachedPropertiesIdOrEnumValue) -{ - Q_ASSERT(!isRemoved()); - Member*e = function->New<Member>(); - e->init(base, name, property, kind, attachedPropertiesIdOrEnumValue); - return e; -} - -inline Stmt *BasicBlock::EXP(Expr *expr) -{ - Q_ASSERT(!isRemoved()); - if (isTerminated()) - return 0; - - Exp *s = function->NewStmt<Exp>(); - s->init(expr); - appendStatement(s); - return s; -} - -inline Stmt *BasicBlock::MOVE(Expr *target, Expr *source) -{ - Q_ASSERT(!isRemoved()); - if (isTerminated()) - return 0; - - Move *s = function->NewStmt<Move>(); - s->init(target, source); - appendStatement(s); - return s; -} - -inline Stmt *BasicBlock::JUMP(BasicBlock *target) -{ - Q_ASSERT(!isRemoved()); - if (isTerminated()) - return 0; - - Jump *s = function->NewStmt<Jump>(); - s->init(target); - appendStatement(s); - - Q_ASSERT(! out.contains(target)); - out.append(target); - - Q_ASSERT(! target->in.contains(this)); - target->in.append(this); - - return s; -} - -inline Stmt *BasicBlock::CJUMP(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse) -{ - Q_ASSERT(!isRemoved()); - if (isTerminated()) - return 0; - - if (iftrue == iffalse) { - MOVE(TEMP(newTemp()), cond); - return JUMP(iftrue); - } - - CJump *s = function->NewStmt<CJump>(); - s->init(cond, iftrue, iffalse, this); - appendStatement(s); - - Q_ASSERT(! out.contains(iftrue)); - out.append(iftrue); - - Q_ASSERT(! iftrue->in.contains(this)); - iftrue->in.append(this); - - Q_ASSERT(! out.contains(iffalse)); - out.append(iffalse); - - Q_ASSERT(! iffalse->in.contains(this)); - iffalse->in.append(this); - - return s; -} - -inline Stmt *BasicBlock::RET(Expr *expr) -{ - Q_ASSERT(!isRemoved()); - if (isTerminated()) - return 0; - - Ret *s = function->NewStmt<Ret>(); - s->init(expr); - appendStatement(s); - return s; -} - -inline Const *Expr::asConst() { return as<Const>(); } -inline String *Expr::asString() { return as<String>(); } -inline RegExp *Expr::asRegExp() { return as<RegExp>(); } -inline Name *Expr::asName() { return as<Name>(); } -inline Temp *Expr::asTemp() { return as<Temp>(); } -inline ArgLocal *Expr::asArgLocal() { return as<ArgLocal>(); } -inline Closure *Expr::asClosure() { return as<Closure>(); } -inline Convert *Expr::asConvert() { return as<Convert>(); } -inline Unop *Expr::asUnop() { return as<Unop>(); } -inline Binop *Expr::asBinop() { return as<Binop>(); } -inline Call *Expr::asCall() { return as<Call>(); } -inline New *Expr::asNew() { return as<New>(); } -inline Subscript *Expr::asSubscript() { return as<Subscript>(); } -inline Member *Expr::asMember() { return as<Member>(); } - -inline Exp *Stmt::asExp() { return as<Exp>(); } -inline Move *Stmt::asMove() { return as<Move>(); } -inline Jump *Stmt::asJump() { return as<Jump>(); } -inline CJump *Stmt::asCJump() { return as<CJump>(); } -inline Ret *Stmt::asRet() { return as<Ret>(); } -inline Phi *Stmt::asPhi() { return as<Phi>(); } - -} // end of namespace IR - -} // end of namespace QV4 - -QT_END_NAMESPACE - -#if defined(QT_POP_CONST) -# pragma pop_macro("CONST") // Restore peace -# undef QT_POP_CONST -#endif - -#endif // QV4IR_P_H diff --git a/src/qml/compiler/qv4jssimplifier.cpp b/src/qml/compiler/qv4jssimplifier.cpp deleted file mode 100644 index 7d09218fe6..0000000000 --- a/src/qml/compiler/qv4jssimplifier.cpp +++ /dev/null @@ -1,384 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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 "qv4jssimplifier_p.h" - -QT_BEGIN_NAMESPACE - -QQmlJavaScriptBindingExpressionSimplificationPass::QQmlJavaScriptBindingExpressionSimplificationPass(const QVector<QmlIR::Object*> &qmlObjects, QV4::IR::Module *jsModule, QV4::Compiler::JSUnitGenerator *unitGenerator) - : qmlObjects(qmlObjects) - , jsModule(jsModule) - , unitGenerator(unitGenerator) -{ - -} - -void QQmlJavaScriptBindingExpressionSimplificationPass::reduceTranslationBindings() -{ - for (int i = 0; i < qmlObjects.count(); ++i) - reduceTranslationBindings(i); - if (!irFunctionsToRemove.isEmpty()) { - QQmlIRFunctionCleanser cleanser(jsModule, qmlObjects, irFunctionsToRemove); - cleanser.clean(); - } -} - -void QQmlJavaScriptBindingExpressionSimplificationPass::reduceTranslationBindings(int objectIndex) -{ - const QmlIR::Object *obj = qmlObjects.at(objectIndex); - - for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { - if (binding->type != QV4::CompiledData::Binding::Type_Script) - continue; - - const int irFunctionIndex = obj->runtimeFunctionIndices.at(binding->value.compiledScriptIndex); - QV4::IR::Function *irFunction = jsModule->functions.at(irFunctionIndex); - if (simplifyBinding(irFunction, binding)) { - irFunctionsToRemove.append(irFunctionIndex); - jsModule->functions[irFunctionIndex] = 0; - delete irFunction; - } - } -} - -void QQmlJavaScriptBindingExpressionSimplificationPass::visitMove(QV4::IR::Move *move) -{ - QV4::IR::Temp *target = move->target->asTemp(); - if (!target || target->kind != QV4::IR::Temp::VirtualRegister) { - discard(); - return; - } - - if (QV4::IR::Call *call = move->source->asCall()) { - if (QV4::IR::Name *n = call->base->asName()) { - if (n->builtin == QV4::IR::Name::builtin_invalid) { - visitFunctionCall(n->id, call->args, target); - return; - } - } - discard(); - return; - } - - if (QV4::IR::Name *n = move->source->asName()) { - if (n->builtin == QV4::IR::Name::builtin_qml_context - || n->builtin == QV4::IR::Name::builtin_qml_imported_scripts_object) { - // these are free of side-effects - return; - } - discard(); - return; - } - - if (!move->source->asTemp() && !move->source->asString() && !move->source->asConst()) { - discard(); - return; - } - - _temps[target->index] = move->source; -} - -void QQmlJavaScriptBindingExpressionSimplificationPass::visitFunctionCall(const QString *name, QV4::IR::ExprList *args, QV4::IR::Temp *target) -{ - // more than one function call? - if (_nameOfFunctionCalled) { - discard(); - return; - } - - _nameOfFunctionCalled = name; - - _functionParameters.clear(); - while (args) { - int slot; - if (QV4::IR::Temp *param = args->expr->asTemp()) { - if (param->kind != QV4::IR::Temp::VirtualRegister) { - discard(); - return; - } - slot = param->index; - _functionParameters.append(slot); - } else if (QV4::IR::Const *param = args->expr->asConst()) { - slot = --_synthesizedConsts; - Q_ASSERT(!_temps.contains(slot)); - _temps[slot] = param; - _functionParameters.append(slot); - } - args = args->next; - } - - _functionCallReturnValue = target->index; -} - -void QQmlJavaScriptBindingExpressionSimplificationPass::visitRet(QV4::IR::Ret *ret) -{ - // nothing initialized earlier? - if (_returnValueOfBindingExpression != -1) { - discard(); - return; - } - QV4::IR::Temp *target = ret->expr->asTemp(); - if (!target || target->kind != QV4::IR::Temp::VirtualRegister) { - discard(); - return; - } - _returnValueOfBindingExpression = target->index; -} - -bool QQmlJavaScriptBindingExpressionSimplificationPass::simplifyBinding(QV4::IR::Function *function, QmlIR::Binding *binding) -{ - _canSimplify = true; - _nameOfFunctionCalled = 0; - _functionParameters.clear(); - _functionCallReturnValue = -1; - _temps.clear(); - _returnValueOfBindingExpression = -1; - _synthesizedConsts = 0; - - // It would seem unlikely that function with some many basic blocks (after optimization) - // consists merely of a qsTr call or a constant value return ;-) - if (function->basicBlockCount() > 10) - return false; - - for (QV4::IR::BasicBlock *bb : function->basicBlocks()) { - for (QV4::IR::Stmt *s : bb->statements()) { - visit(s); - if (!_canSimplify) - return false; - } - } - - if (_returnValueOfBindingExpression == -1) - return false; - - if (_nameOfFunctionCalled) { - if (_functionCallReturnValue != _returnValueOfBindingExpression) - return false; - return detectTranslationCallAndConvertBinding(binding); - } - - return false; -} - -bool QQmlJavaScriptBindingExpressionSimplificationPass::detectTranslationCallAndConvertBinding(QmlIR::Binding *binding) -{ - if (*_nameOfFunctionCalled == QLatin1String("qsTr")) { - QString translation; - QV4::CompiledData::TranslationData translationData; - translationData.number = -1; - translationData.commentIndex = 0; // empty string - - QVector<int>::ConstIterator param = _functionParameters.constBegin(); - QVector<int>::ConstIterator end = _functionParameters.constEnd(); - if (param == end) - return false; - - QV4::IR::String *stringParam = _temps[*param]->asString(); - if (!stringParam) - return false; - - translation = *stringParam->value; - - ++param; - if (param != end) { - stringParam = _temps[*param]->asString(); - if (!stringParam) - return false; - translationData.commentIndex = unitGenerator->registerString(*stringParam->value); - ++param; - - if (param != end) { - QV4::IR::Const *constParam = _temps[*param]->asConst(); - if (!constParam || constParam->type != QV4::IR::SInt32Type) - return false; - - translationData.number = int(constParam->value); - ++param; - } - } - - if (param != end) - return false; - - binding->type = QV4::CompiledData::Binding::Type_Translation; - binding->stringIndex = unitGenerator->registerString(translation); - binding->value.translationData = translationData; - return true; - } else if (*_nameOfFunctionCalled == QLatin1String("qsTrId")) { - QString id; - QV4::CompiledData::TranslationData translationData; - translationData.number = -1; - translationData.commentIndex = 0; // empty string, but unused - - QVector<int>::ConstIterator param = _functionParameters.constBegin(); - QVector<int>::ConstIterator end = _functionParameters.constEnd(); - if (param == end) - return false; - - QV4::IR::String *stringParam = _temps[*param]->asString(); - if (!stringParam) - return false; - - id = *stringParam->value; - - ++param; - if (param != end) { - QV4::IR::Const *constParam = _temps[*param]->asConst(); - if (!constParam || constParam->type != QV4::IR::SInt32Type) - return false; - - translationData.number = int(constParam->value); - ++param; - } - - if (param != end) - return false; - - binding->type = QV4::CompiledData::Binding::Type_TranslationById; - binding->stringIndex = unitGenerator->registerString(id); - binding->value.translationData = translationData; - return true; - } else if (*_nameOfFunctionCalled == QLatin1String("QT_TR_NOOP") || *_nameOfFunctionCalled == QLatin1String("QT_TRID_NOOP")) { - QVector<int>::ConstIterator param = _functionParameters.constBegin(); - QVector<int>::ConstIterator end = _functionParameters.constEnd(); - if (param == end) - return false; - - QV4::IR::String *stringParam = _temps[*param]->asString(); - if (!stringParam) - return false; - - ++param; - if (param != end) - return false; - - binding->type = QV4::CompiledData::Binding::Type_String; - binding->stringIndex = unitGenerator->registerString(*stringParam->value); - return true; - } else if (*_nameOfFunctionCalled == QLatin1String("QT_TRANSLATE_NOOP")) { - QVector<int>::ConstIterator param = _functionParameters.constBegin(); - QVector<int>::ConstIterator end = _functionParameters.constEnd(); - if (param == end) - return false; - - ++param; - if (param == end) - return false; - - QV4::IR::String *stringParam = _temps[*param]->asString(); - if (!stringParam) - return false; - - ++param; - if (param != end) - return false; - - binding->type = QV4::CompiledData::Binding::Type_String; - binding->stringIndex = unitGenerator->registerString(*stringParam->value); - return true; - } - return false; -} - -QQmlIRFunctionCleanser::QQmlIRFunctionCleanser(QV4::IR::Module *module, const QVector<QmlIR::Object *> &qmlObjects, const QVector<int> &functionsToRemove) - : module(module) - , qmlObjects(qmlObjects) - , functionsToRemove(functionsToRemove) -{ -} - -void QQmlIRFunctionCleanser::clean() -{ - QVector<QV4::IR::Function*> newFunctions; - newFunctions.reserve(module->functions.count() - functionsToRemove.count()); - - newFunctionIndices.resize(module->functions.count()); - - for (int i = 0; i < module->functions.count(); ++i) { - QV4::IR::Function *f = module->functions.at(i); - Q_ASSERT(f || functionsToRemove.contains(i)); - if (f) { - newFunctionIndices[i] = newFunctions.count(); - newFunctions << f; - } - } - - module->functions = newFunctions; - - for (QV4::IR::Function *function : qAsConst(module->functions)) { - for (QV4::IR::BasicBlock *block : function->basicBlocks()) { - for (QV4::IR::Stmt *s : block->statements()) { - visit(s); - } - } - } - - for (QmlIR::Object *obj : qmlObjects) { - 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::visit(QV4::IR::Expr *e) -{ - 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/qv4jssimplifier_p.h b/src/qml/compiler/qv4jssimplifier_p.h deleted file mode 100644 index ae8d74135c..0000000000 --- a/src/qml/compiler/qv4jssimplifier_p.h +++ /dev/null @@ -1,154 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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 QV4JSSIMPLIFIER -#define QV4JSSIMPLIFIER - -// -// 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 "qqmlirbuilder_p.h" - -QT_BEGIN_NAMESPACE - -namespace QmlIR { -struct Document; -} - -namespace QV4 { -namespace CompiledData { -struct QmlUnit; -struct Location; -} -} - -class QQmlJavaScriptBindingExpressionSimplificationPass -{ -public: - QQmlJavaScriptBindingExpressionSimplificationPass(const QVector<QmlIR::Object*> &qmlObjects, QV4::IR::Module *jsModule, QV4::Compiler::JSUnitGenerator *unitGenerator); - - void reduceTranslationBindings(); - -private: - void reduceTranslationBindings(int objectIndex); - - 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); - - void discard() { _canSimplify = false; } - - bool simplifyBinding(QV4::IR::Function *function, QmlIR::Binding *binding); - bool detectTranslationCallAndConvertBinding(QmlIR::Binding *binding); - - const QVector<QmlIR::Object*> &qmlObjects; - QV4::IR::Module *jsModule; - QV4::Compiler::JSUnitGenerator *unitGenerator; - - bool _canSimplify; - const QString *_nameOfFunctionCalled; - QVector<int> _functionParameters; - int _functionCallReturnValue; - - QHash<int, QV4::IR::Expr*> _temps; - int _returnValueOfBindingExpression; - int _synthesizedConsts; - - QVector<int> irFunctionsToRemove; -}; - -class QQmlIRFunctionCleanser -{ -public: - QQmlIRFunctionCleanser(QV4::IR::Module *module, const QVector<QmlIR::Object*> &qmlObjects, const QVector<int> &functionsToRemove); - - void clean(); - -private: - virtual void visitMove(QV4::IR::Move *s) { - visit(s->source); - visit(s->target); - } - - void visit(QV4::IR::Stmt *s); - void visit(QV4::IR::Expr *e); - -private: - QV4::IR::Module *module; - const QVector<QmlIR::Object*> &qmlObjects; - const QVector<int> &functionsToRemove; - - QVector<int> newFunctionIndices; -}; - -QT_END_NAMESPACE - -#endif // QV4JSSIMPLIFIER diff --git a/src/qml/compiler/qv4ssa.cpp b/src/qml/compiler/qv4ssa.cpp deleted file mode 100644 index fc136b09ff..0000000000 --- a/src/qml/compiler/qv4ssa.cpp +++ /dev/null @@ -1,5848 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// When building with debug code, the macro below will enable debug helpers when using libc++. -// For example, the std::vector<T>::operator[] will use _LIBCPP_ASSERT to check if the index is -// within the array bounds. Note that this only works reliably with OSX 10.9 or later. -//#define _LIBCPP_DEBUG2 2 - -#include "qv4ssa_p.h" -#include "qv4isel_util_p.h" -#include "qv4util_p.h" - -#include <QtCore/QBuffer> -#include <QtCore/QCoreApplication> -#include <QtCore/QStringList> -#include <QtCore/QSet> -#include <QtCore/QLinkedList> -#include <QtCore/QStack> -#include <qv4runtime_p.h> -#include <cmath> -#include <iostream> -#include <cassert> - -QT_USE_NAMESPACE - -using namespace QV4; -using namespace IR; - -namespace { - -enum { DebugMoveMapping = 0 }; - -#ifdef QT_NO_DEBUG -enum { DoVerification = 0 }; -#else -enum { DoVerification = 1 }; -#endif - -static void showMeTheCode(IR::Function *function, const char *marker) -{ - static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_IR"); - if (showCode) { - qDebug() << marker; - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream stream(&buf); - IRPrinter(&stream).print(function); - stream << endl; - qDebug("%s", buf.data().constData()); - } -} - -class ProcessedBlocks -{ - BitVector processed; - -public: - ProcessedBlocks(IR::Function *function) - : processed(function->basicBlockCount(), false) - {} - - bool alreadyProcessed(BasicBlock *bb) const - { - Q_ASSERT(bb); - - return processed.at(bb->index()); - } - - void markAsProcessed(BasicBlock *bb) - { - processed.setBit(bb->index()); - } -}; - -class BasicBlockSet -{ - typedef BitVector Flags; - - QVarLengthArray<int, 8> blockNumbers; - Flags *blockFlags; - IR::Function *function; - enum { MaxVectorCapacity = 8 }; - -public: - class const_iterator - { - const BasicBlockSet &set; - // ### These two members could go into a union, but clang won't compile (https://codereview.qt-project.org/#change,74259) - QVarLengthArray<int, 8>::const_iterator numberIt; - int flagIt; - - friend class BasicBlockSet; - const_iterator(const BasicBlockSet &set, bool end) - : set(set) - { - if (end || !set.function) { - if (!set.blockFlags) - numberIt = set.blockNumbers.end(); - else - flagIt = set.blockFlags->size(); - } else { - if (!set.blockFlags) - numberIt = set.blockNumbers.begin(); - else - findNextWithFlags(0); - } - } - - void findNextWithFlags(int start) - { - flagIt = set.blockFlags->findNext(start, true, /*wrapAround = */false); - Q_ASSERT(flagIt <= set.blockFlags->size()); - } - - public: - BasicBlock *operator*() const - { - if (!set.blockFlags) { - return set.function->basicBlock(*numberIt); - } else { - Q_ASSERT(flagIt <= set.function->basicBlockCount()); - return set.function->basicBlock(flagIt); - } - } - - bool operator==(const const_iterator &other) const - { - if (&set != &other.set) - return false; - if (!set.blockFlags) - return numberIt == other.numberIt; - else - return flagIt == other.flagIt; - } - - bool operator!=(const const_iterator &other) const - { return !(*this == other); } - - const_iterator &operator++() - { - if (!set.blockFlags) - ++numberIt; - else - findNextWithFlags(flagIt + 1); - - return *this; - } - }; - - friend class const_iterator; - -public: - BasicBlockSet(IR::Function *f = 0): blockFlags(0), function(0) - { - if (f) - init(f); - } - -#ifdef Q_COMPILER_RVALUE_REFS - BasicBlockSet(BasicBlockSet &&other): blockFlags(0) - { - std::swap(blockNumbers, other.blockNumbers); - std::swap(blockFlags, other.blockFlags); - std::swap(function, other.function); - } -#endif // Q_COMPILER_RVALUE_REFS - - BasicBlockSet(const BasicBlockSet &other) - : blockFlags(0) - , function(other.function) - { - if (other.blockFlags) - blockFlags = new Flags(*other.blockFlags); - blockNumbers = other.blockNumbers; - } - - BasicBlockSet &operator=(const BasicBlockSet &other) - { - if (blockFlags) { - delete blockFlags; - blockFlags = 0; - } - function = other.function; - if (other.blockFlags) - blockFlags = new Flags(*other.blockFlags); - blockNumbers = other.blockNumbers; - return *this; - } - - ~BasicBlockSet() - { - delete blockFlags; - } - - void init(IR::Function *f) - { - Q_ASSERT(!function); - Q_ASSERT(f); - function = f; - } - - bool empty() const - { - return begin() == end(); - } - - void insert(BasicBlock *bb) - { - Q_ASSERT(function); - - if (blockFlags) { - blockFlags->setBit(bb->index()); - return; - } - - for (int i = 0; i < blockNumbers.size(); ++i) { - if (blockNumbers[i] == bb->index()) - return; - } - - if (blockNumbers.size() == MaxVectorCapacity) { - blockFlags = new Flags(function->basicBlockCount(), false); - for (int i = 0; i < blockNumbers.size(); ++i) { - blockFlags->setBit(blockNumbers[i]); - } - blockNumbers.clear(); - blockFlags->setBit(bb->index()); - } else { - blockNumbers.append(bb->index()); - } - } - - void remove(BasicBlock *bb) - { - Q_ASSERT(function); - - if (blockFlags) { - blockFlags->clearBit(bb->index()); - return; - } - - for (int i = 0; i < blockNumbers.size(); ++i) { - if (blockNumbers[i] == bb->index()) { - blockNumbers.remove(i); - return; - } - } - } - - const_iterator begin() const { return const_iterator(*this, false); } - const_iterator end() const { return const_iterator(*this, true); } - - void collectValues(std::vector<BasicBlock *> &bbs) const - { - Q_ASSERT(function); - - for (const_iterator it = begin(), eit = end(); it != eit; ++it) - bbs.push_back(*it); - } - - bool contains(BasicBlock *bb) const - { - Q_ASSERT(function); - - if (blockFlags) - return blockFlags->at(bb->index()); - - for (int i = 0; i < blockNumbers.size(); ++i) { - if (blockNumbers[i] == bb->index()) - return true; - } - - return false; - } -}; - -class DominatorTree -{ - enum { - DebugDominatorFrontiers = 0, - DebugImmediateDominators = 0, - - DebugCodeCanUseLotsOfCpu = 0 - }; - - typedef int BasicBlockIndex; - enum { InvalidBasicBlockIndex = -1 }; - - struct Data - { - int N; - std::vector<int> dfnum; // BasicBlock index -> dfnum - std::vector<int> vertex; - std::vector<BasicBlockIndex> parent; // BasicBlock index -> parent BasicBlock index - std::vector<BasicBlockIndex> ancestor; // BasicBlock index -> ancestor BasicBlock index - std::vector<BasicBlockIndex> best; // BasicBlock index -> best BasicBlock index - std::vector<BasicBlockIndex> semi; // BasicBlock index -> semi dominator BasicBlock index - std::vector<BasicBlockIndex> samedom; // BasicBlock index -> same dominator BasicBlock index - - Data(): N(0) {} - }; - - IR::Function *function; - QScopedPointer<Data> d; - std::vector<BasicBlockIndex> idom; // BasicBlock index -> immediate dominator BasicBlock index - std::vector<BasicBlockSet> DF; // BasicBlock index -> dominator frontier - - struct DFSTodo { - BasicBlockIndex node, parent; - - DFSTodo() - : node(InvalidBasicBlockIndex) - , parent(InvalidBasicBlockIndex) - {} - - DFSTodo(BasicBlockIndex node, BasicBlockIndex parent) - : node(node) - , parent(parent) - {} - }; - - void DFS(BasicBlockIndex node) { - std::vector<DFSTodo> worklist; - worklist.reserve(d->vertex.capacity() / 2); - DFSTodo todo(node, InvalidBasicBlockIndex); - - while (true) { - BasicBlockIndex n = todo.node; - - if (d->dfnum[n] == 0) { - d->dfnum[n] = d->N; - d->vertex[d->N] = n; - d->parent[n] = todo.parent; - ++d->N; - const BasicBlock::OutgoingEdges &out = function->basicBlock(n)->out; - for (int i = out.size() - 1; i > 0; --i) - worklist.push_back(DFSTodo(out[i]->index(), n)); - - if (out.size() > 0) { - todo.node = out.first()->index(); - todo.parent = n; - continue; - } - } - - if (worklist.empty()) - break; - - todo = worklist.back(); - worklist.pop_back(); - } - } - - BasicBlockIndex ancestorWithLowestSemi(BasicBlockIndex v, std::vector<BasicBlockIndex> &worklist) { - worklist.clear(); - for (BasicBlockIndex it = v; it != InvalidBasicBlockIndex; it = d->ancestor[it]) - worklist.push_back(it); - - if (worklist.size() < 2) - return d->best[v]; - - BasicBlockIndex b = InvalidBasicBlockIndex; - BasicBlockIndex last = worklist.back(); - Q_ASSERT(worklist.size() <= INT_MAX); - for (int it = static_cast<int>(worklist.size()) - 2; it >= 0; --it) { - BasicBlockIndex bbIt = worklist[it]; - d->ancestor[bbIt] = last; - BasicBlockIndex &best_it = d->best[bbIt]; - if (b != InvalidBasicBlockIndex && d->dfnum[d->semi[b]] < d->dfnum[d->semi[best_it]]) - best_it = b; - else - b = best_it; - } - return b; - } - - void link(BasicBlockIndex p, BasicBlockIndex n) { - d->ancestor[n] = p; - d->best[n] = n; - } - - void calculateIDoms() { - Q_ASSERT(function->basicBlock(0)->in.isEmpty()); - - const int bbCount = function->basicBlockCount(); - d->vertex = std::vector<int>(bbCount, InvalidBasicBlockIndex); - d->parent = std::vector<int>(bbCount, InvalidBasicBlockIndex); - d->dfnum = std::vector<int>(size_t(bbCount), 0); - d->semi = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex); - d->ancestor = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex); - idom = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex); - d->samedom = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex); - d->best = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex); - - QHash<BasicBlockIndex, std::vector<BasicBlockIndex> > bucket; - bucket.reserve(bbCount); - - DFS(function->basicBlock(0)->index()); - Q_ASSERT(d->N == function->liveBasicBlocksCount()); - - std::vector<BasicBlockIndex> worklist; - worklist.reserve(d->vertex.capacity() / 2); - - for (int i = d->N - 1; i > 0; --i) { - BasicBlockIndex n = d->vertex[i]; - BasicBlockIndex p = d->parent[n]; - BasicBlockIndex s = p; - - for (BasicBlock *v : function->basicBlock(n)->in) { - BasicBlockIndex ss = InvalidBasicBlockIndex; - if (d->dfnum[v->index()] <= d->dfnum[n]) - ss = v->index(); - else - ss = d->semi[ancestorWithLowestSemi(v->index(), worklist)]; - if (d->dfnum[ss] < d->dfnum[s]) - s = ss; - } - d->semi[n] = s; - bucket[s].push_back(n); - link(p, n); - if (bucket.contains(p)) { - for (BasicBlockIndex v : bucket[p]) { - BasicBlockIndex y = ancestorWithLowestSemi(v, worklist); - BasicBlockIndex semi_v = d->semi[v]; - if (d->semi[y] == semi_v) - idom[v] = semi_v; - else - d->samedom[v] = y; - } - bucket.remove(p); - } - } - - for (int i = 1; i < d->N; ++i) { - BasicBlockIndex n = d->vertex[i]; - Q_ASSERT(n != InvalidBasicBlockIndex); - Q_ASSERT(!bucket.contains(n)); - Q_ASSERT(d->ancestor[n] != InvalidBasicBlockIndex - && ((d->semi[n] != InvalidBasicBlockIndex - && d->dfnum[d->ancestor[n]] <= d->dfnum[d->semi[n]]) || d->semi[n] == n)); - BasicBlockIndex sdn = d->samedom[n]; - if (sdn != InvalidBasicBlockIndex) - idom[n] = idom[sdn]; - } - - dumpImmediateDominators(); - } - - struct NodeProgress { - std::vector<BasicBlockIndex> children; - std::vector<BasicBlockIndex> todo; - }; - -public: - DominatorTree(IR::Function *function) - : function(function) - , d(new Data) - { - calculateIDoms(); - d.reset(); - } - - void computeDF() { - DF.resize(function->basicBlockCount()); - - // compute children of each node in the dominator tree - std::vector<std::vector<BasicBlockIndex> > children; // BasicBlock index -> children - children.resize(function->basicBlockCount()); - for (BasicBlock *n : function->basicBlocks()) { - if (n->isRemoved()) - continue; - const BasicBlockIndex nodeIndex = n->index(); - Q_ASSERT(function->basicBlock(nodeIndex) == n); - const BasicBlockIndex nodeDominator = idom[nodeIndex]; - if (nodeDominator == InvalidBasicBlockIndex) - continue; // there is no dominator to add this node to as a child (e.g. the start node) - children[nodeDominator].push_back(nodeIndex); - } - - // Fill the worklist and initialize the node status for each basic-block - std::vector<NodeProgress> nodeStatus; - nodeStatus.resize(function->basicBlockCount()); - std::vector<BasicBlockIndex> worklist; - worklist.reserve(function->basicBlockCount()); - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) - continue; - BasicBlockIndex nodeIndex = bb->index(); - worklist.push_back(nodeIndex); - NodeProgress &np = nodeStatus[nodeIndex]; - np.children = children[nodeIndex]; - np.todo = children[nodeIndex]; - } - - BitVector DF_done(function->basicBlockCount(), false); - - while (!worklist.empty()) { - BasicBlockIndex node = worklist.back(); - - if (DF_done.at(node)) { - worklist.pop_back(); - continue; - } - - NodeProgress &np = nodeStatus[node]; - std::vector<BasicBlockIndex>::iterator it = np.todo.begin(); - while (it != np.todo.end()) { - if (DF_done.at(*it)) { - it = np.todo.erase(it); - } else { - worklist.push_back(*it); - break; - } - } - - if (np.todo.empty()) { - BasicBlockSet &S = DF[node]; - S.init(function); - for (BasicBlock *y : function->basicBlock(node)->out) - if (idom[y->index()] != node) - S.insert(y); - for (BasicBlockIndex child : np.children) { - const BasicBlockSet &ws = DF[child]; - for (BasicBlockSet::const_iterator it = ws.begin(), eit = ws.end(); it != eit; ++it) { - BasicBlock *w = *it; - const BasicBlockIndex wIndex = w->index(); - if (node == wIndex || !dominates(node, w->index())) - S.insert(w); - } - } - DF_done.setBit(node); - worklist.pop_back(); - } - } - - if (DebugDominatorFrontiers) { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - qout << "Dominator Frontiers:" << endl; - for (BasicBlock *n : function->basicBlocks()) { - if (n->isRemoved()) - continue; - - qout << "\tDF[" << n->index() << "]: {"; - const BasicBlockSet &SList = DF[n->index()]; - for (BasicBlockSet::const_iterator i = SList.begin(), ei = SList.end(); i != ei; ++i) { - if (i != SList.begin()) - qout << ", "; - qout << (*i)->index(); - } - qout << "}" << endl; - } - qDebug("%s", buf.data().constData()); - } - - if (DebugDominatorFrontiers && DebugCodeCanUseLotsOfCpu) { - for (BasicBlock *n : function->basicBlocks()) { - if (n->isRemoved()) - continue; - const BasicBlockSet &fBlocks = DF[n->index()]; - for (BasicBlockSet::const_iterator it = fBlocks.begin(), eit = fBlocks.end(); it != eit; ++it) { - BasicBlock *fBlock = *it; - Q_ASSERT(!dominates(n, fBlock) || fBlock == n); - bool hasDominatedSucc = false; - for (BasicBlock *succ : fBlock->in) { - if (dominates(n, succ)) { - hasDominatedSucc = true; - break; - } - } - if (!hasDominatedSucc) { - qDebug("%d in DF[%d] has no dominated predecessors", fBlock->index(), n->index()); - } - Q_ASSERT(hasDominatedSucc); - } - } - } - } - - const BasicBlockSet &dominatorFrontier(BasicBlock *n) const { - return DF[n->index()]; - } - - BasicBlock *immediateDominator(BasicBlock *bb) const { - const BasicBlockIndex idx = idom[bb->index()]; - if (idx == -1) - return 0; - return function->basicBlock(idx); - } - - void dumpImmediateDominators() const - { - if (DebugImmediateDominators) { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - qout << "Immediate dominators:" << endl; - for (BasicBlock *to : function->basicBlocks()) { - if (to->isRemoved()) - continue; - - qout << '\t'; - BasicBlockIndex from = idom.at(to->index()); - if (from != InvalidBasicBlockIndex) - qout << from; - else - qout << "(none)"; - qout << " dominates " << to->index() << endl; - } - qDebug("%s", buf.data().constData()); - } - } - - void setImmediateDominator(BasicBlock *bb, BasicBlock *newDominator) - { - Q_ASSERT(bb->index() >= 0); - Q_ASSERT(!newDominator || newDominator->index() >= 0); - - if (static_cast<std::vector<BasicBlockIndex>::size_type>(bb->index()) >= idom.size()) { - // This is a new block, probably introduced by edge splitting. So, we'll have to grow - // the array before inserting the immediate dominator. - idom.resize(function->basicBlockCount(), InvalidBasicBlockIndex); - } - - const BasicBlockIndex newIdx = newDominator ? newDominator->index() : InvalidBasicBlockIndex; - if (DebugImmediateDominators) - qDebug() << "Setting idom of" << bb->index() << "from" << idom[bb->index()] << "to" << newIdx; - idom[bb->index()] = newIdx; - } - - void collectSiblings(BasicBlock *node, BasicBlockSet &siblings) - { - siblings.insert(node); - const BasicBlockIndex dominator = idom[node->index()]; - if (dominator == InvalidBasicBlockIndex) - return; - for (size_t i = 0, ei = idom.size(); i != ei; ++i) { - if (idom[i] == dominator) { - BasicBlock *bb = function->basicBlock(int(i)); - if (!bb->isRemoved()) - siblings.insert(bb); - } - } - } - - void recalculateIDoms(const BasicBlockSet &nodes, BasicBlock *limit = 0) - { - const BasicBlockIndex limitIndex = limit ? limit->index() : InvalidBasicBlockIndex; - BasicBlockSet todo(nodes), postponed(function); - while (!todo.empty()) - recalculateIDom(*todo.begin(), todo, postponed, limitIndex); - } - - bool dominates(BasicBlock *dominator, BasicBlock *dominated) const { - return dominates(dominator->index(), dominated->index()); - } - - struct Cmp { - std::vector<int> *nodeDepths; - Cmp(std::vector<int> *nodeDepths) - : nodeDepths(nodeDepths) - { Q_ASSERT(nodeDepths); } - bool operator()(BasicBlock *one, BasicBlock *two) const - { - if (one->isRemoved()) - return false; - if (two->isRemoved()) - return true; - return nodeDepths->at(one->index()) > nodeDepths->at(two->index()); - } - }; - - // Calculate a depth-first iteration order on the nodes of the dominator tree. - // - // The order of the nodes in the vector is not the same as one where a recursive depth-first - // iteration is done on a tree. Rather, the nodes are (reverse) sorted on tree depth. - // So for the: - // 1 dominates 2 - // 2 dominates 3 - // 3 dominates 4 - // 2 dominates 5 - // the order will be: - // 4, 3, 5, 2, 1 - // or: - // 4, 5, 3, 2, 1 - // So the order of nodes on the same depth is undefined, but it will be after the nodes - // they dominate, and before the nodes that dominate them. - // - // The reason for this order is that a proper DFS pre-/post-order would require inverting - // the idom vector by either building a real tree datastructure or by searching the idoms - // for siblings and children. Both have a higher time complexity than sorting by depth. - QVector<BasicBlock *> calculateDFNodeIterOrder() const - { - std::vector<int> depths = calculateNodeDepths(); - QVector<BasicBlock *> order = function->basicBlocks(); - std::sort(order.begin(), order.end(), Cmp(&depths)); - for (int i = 0; i < order.size(); ) { - if (order[i]->isRemoved()) - order.remove(i); - else - ++i; - } - 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) - Q_ASSERT(dominated != InvalidBasicBlockIndex); - - if (dominator == dominated) - return false; - - for (BasicBlockIndex it = idom[dominated]; it != InvalidBasicBlockIndex; it = idom[it]) { - if (it == dominator) - return true; - } - - return false; - } - - // Algorithm: - // - for each node: - // - get the depth of a node. If it's unknown (-1): - // - get the depth of the immediate dominator. - // - if that's unknown too, calculate it by calling calculateNodeDepth - // - set the current node's depth to that of immediate dominator + 1 - std::vector<int> calculateNodeDepths() const - { - std::vector<int> nodeDepths(size_t(function->basicBlockCount()), -1); - nodeDepths[0] = 0; - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) - continue; - - int &bbDepth = nodeDepths[bb->index()]; - if (bbDepth == -1) { - const int immDom = idom[bb->index()]; - int immDomDepth = nodeDepths[immDom]; - if (immDomDepth == -1) - immDomDepth = calculateNodeDepth(immDom, nodeDepths); - bbDepth = immDomDepth + 1; - } - } - return nodeDepths; - } - - // Algorithm: - // - search for the first dominator of a node that has a known depth. As all nodes are - // reachable from the start node, and that node's depth is 0, this is finite. - // - while doing that search, put all unknown nodes in the worklist - // - pop all nodes from the worklist, and set their depth to the previous' (== dominating) - // node's depth + 1 - // This way every node's depth is calculated once, and the complexity is O(n). - int calculateNodeDepth(int nodeIdx, std::vector<int> &nodeDepths) const - { - std::vector<int> worklist; - worklist.reserve(8); - int depth = -1; - - do { - worklist.push_back(nodeIdx); - nodeIdx = idom[nodeIdx]; - depth = nodeDepths[nodeIdx]; - } while (depth == -1); - - for (std::vector<int>::const_reverse_iterator it = worklist.rbegin(), eit = worklist.rend(); it != eit; ++it) - nodeDepths[*it] = ++depth; - - return depth; - } - - // The immediate-dominator recalculation is used when edges are removed from the CFG. See - // [Ramalingam] for a description. Note that instead of calculating the priority, a recursive - // algorithm is used: when recalculating the immediate dominator of a node by looking for the - // least-common ancestor, and a node is hit that also needs recalculation, a recursive call - // is done to calculate that nodes immediate dominator first. - // - // Note that this simplified algorithm cannot cope with back-edges. It only works for - // non-looping edges (which is our use-case). - void recalculateIDom(BasicBlock *node, BasicBlockSet &todo, BasicBlockSet &postponed, BasicBlockIndex limit) { - Q_ASSERT(!postponed.contains(node)); - Q_ASSERT(todo.contains(node)); - todo.remove(node); - - if (node->in.size() == 1) { - // Special case: if the node has only one incoming edge, then that is the immediate - // dominator. - setImmediateDominator(node, node->in.first()); - return; - } - - std::vector<BasicBlockIndex> prefix; - prefix.reserve(32); - - for (BasicBlock *in : node->in) { - if (node == in) // back-edge to self - continue; - if (dominates(node->index(), in->index())) // a known back-edge - continue; - - if (prefix.empty()) { - calculatePrefix(node, in, prefix, todo, postponed, limit); - - if (!prefix.empty()) { - std::reverse(prefix.begin(), prefix.end()); - Q_ASSERT(!prefix.empty()); - Q_ASSERT(prefix.front() == limit || limit == InvalidBasicBlockIndex); - } - } else { - std::vector<BasicBlockIndex> anotherPrefix; - anotherPrefix.reserve(prefix.size()); - calculatePrefix(node, in, anotherPrefix, todo, postponed, limit); - - if (!anotherPrefix.empty()) - commonPrefix(prefix, anotherPrefix); - } - } - - Q_ASSERT(!prefix.empty()); - idom[node->index()] = prefix.back(); - } - - void calculatePrefix(BasicBlock *node, BasicBlock *in, std::vector<BasicBlockIndex> &prefix, BasicBlockSet &todo, BasicBlockSet &postponed, BasicBlockIndex limit) - { - for (BasicBlockIndex it = in->index(); it != InvalidBasicBlockIndex; it = idom[it]) { - prefix.push_back(it); - if (it == limit) - return; - BasicBlock *n = function->basicBlock(it); - if (postponed.contains(n)) { // possible back-edge, bail out. - prefix.clear(); - return; - } - if (todo.contains(n)) { - postponed.insert(node); - recalculateIDom(n, todo, postponed, limit); - postponed.remove(node); - } - } - } - - // Calculate the LCA (Least Common Ancestor) by finding the longest common prefix between two - // dominator chains. Note that "anotherPrefix" has the node's immediate dominator first, while - // "bestPrefix" has it last (meaning: is in reverse order). The reason for this is that removing - // nodes from "bestPrefix" is cheaper because it's done at the end of the vector, while - // reversing all "anotherPrefix" nodes would take unnecessary time. - static void commonPrefix(std::vector<BasicBlockIndex> &bestPrefix, const std::vector<BasicBlockIndex> &anotherPrefix) - { - const size_t anotherSize = anotherPrefix.size(); - size_t minLen = qMin(bestPrefix.size(), anotherPrefix.size()); - while (minLen != 0) { - --minLen; - if (bestPrefix[minLen] == anotherPrefix[anotherSize - minLen - 1]) { - ++minLen; - break; - } - } - if (minLen != bestPrefix.size()) - bestPrefix.erase(bestPrefix.begin() + minLen, bestPrefix.end()); - } -}; - -class VariableCollector { - std::vector<Temp> _allTemps; - std::vector<BasicBlockSet> _defsites; - std::vector<std::vector<int> > A_orig; - BitVector nonLocals; - BitVector killed; - - BasicBlock *currentBB; - bool isCollectable(Temp *t) const - { - Q_UNUSED(t); - Q_ASSERT(t->kind != Temp::PhysicalRegister && t->kind != Temp::StackSlot); - return true; - } - - void addDefInCurrentBlock(Temp *t) - { - std::vector<int> &temps = A_orig[currentBB->index()]; - if (std::find(temps.begin(), temps.end(), t->index) == temps.end()) - temps.push_back(t->index); - } - - void addTemp(Temp *t) - { - if (_allTemps[t->index].kind == Temp::Invalid) - _allTemps[t->index] = *t; - } - -public: - VariableCollector(IR::Function *function) - { - _allTemps.resize(function->tempCount); - _defsites.resize(function->tempCount); - for (int i = 0; i < function->tempCount; ++i) - _defsites[i].init(function); - nonLocals.resize(function->tempCount); - const size_t ei = function->basicBlockCount(); - A_orig.resize(ei); - for (size_t i = 0; i != ei; ++i) - A_orig[i].reserve(8); - - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) - continue; - - currentBB = bb; - killed.assign(function->tempCount, false); - for (Stmt *s : bb->statements()) - visit(s); - } - } - - const std::vector<Temp> &allTemps() const - { return _allTemps; } - - void collectDefSites(const Temp &n, std::vector<BasicBlock *> &bbs) const { - Q_ASSERT(!n.isInvalid()); - Q_ASSERT(n.index < _defsites.size()); - _defsites[n.index].collectValues(bbs); - } - - const std::vector<int> &inBlock(BasicBlock *n) const - { - return A_orig.at(n->index()); - } - - bool isNonLocal(const Temp &var) const - { - Q_ASSERT(!var.isInvalid()); - Q_ASSERT(static_cast<int>(var.index) < nonLocals.size()); - return nonLocals.at(var.index); - } - -private: - void visit(Stmt *s) - { - if (s->asPhi()) { - // nothing to do - } else if (auto move = s->asMove()) { - visit(move->source); - - if (Temp *t = move->target->asTemp()) { - addTemp(t); - - if (isCollectable(t)) { - _defsites[t->index].insert(currentBB); - addDefInCurrentBlock(t); - - // For semi-pruned SSA: - killed.setBit(t->index); - } - } else { - visit(move->target); - } - } else { - STMT_VISIT_ALL_KINDS(s) - } - } - - void visit(Expr *e) - { - if (auto t = e->asTemp()) { - addTemp(t); - - if (isCollectable(t)) { - if (!killed.at(t->index)) { - nonLocals.setBit(t->index); - } - } - } else { - EXPR_VISIT_ALL_KINDS(e); - } - } -}; - -struct UntypedTemp { - Temp temp; - UntypedTemp() {} - UntypedTemp(const Temp &t): temp(t) {} -}; -inline bool operator==(const UntypedTemp &t1, const UntypedTemp &t2) Q_DECL_NOTHROW -{ return t1.temp.index == t2.temp.index && t1.temp.kind == t2.temp.kind; } -inline bool operator!=(const UntypedTemp &t1, const UntypedTemp &t2) Q_DECL_NOTHROW -{ return !(t1 == t2); } - -class DefUses -{ -public: - struct DefUse { - DefUse() - : defStmt(0) - , blockOfStatement(0) - { uses.reserve(8); } - Temp temp; - Stmt *defStmt; - BasicBlock *blockOfStatement; - QVector<Stmt *> uses; - - bool isValid() const - { return temp.kind != Temp::Invalid; } - - void clear() - { defStmt = 0; blockOfStatement = 0; uses.clear(); } - }; - -private: - std::vector<DefUse> _defUses; - typedef QVarLengthArray<Temp, 4> Temps; - std::vector<Temps> _usesPerStatement; - - void ensure(Temp *newTemp) - { - if (_defUses.size() <= newTemp->index) { - _defUses.resize(newTemp->index + 1); - } - } - - void ensure(Stmt *s) - { - Q_ASSERT(s->id() >= 0); - if (static_cast<unsigned>(s->id()) >= _usesPerStatement.size()) { - _usesPerStatement.resize(s->id() + 1); - } - } - - void addUseForStatement(Stmt *s, const Temp &var) - { - ensure(s); - _usesPerStatement[s->id()].push_back(var); - } - -public: - DefUses(IR::Function *function) - { - _usesPerStatement.resize(function->statementCount()); - _defUses.resize(function->tempCount); - } - - void cleanup() - { - for (size_t i = 0, ei = _defUses.size(); i != ei; ++i) { - DefUse &defUse = _defUses[i]; - if (defUse.isValid() && !defUse.defStmt) - defUse.clear(); - } - } - - unsigned statementCount() const - { return unsigned(_usesPerStatement.size()); } - - unsigned tempCount() const - { return unsigned(_defUses.size()); } - - const Temp &temp(int idx) const - { return _defUses[idx].temp; } - - void addDef(Temp *newTemp, Stmt *defStmt, BasicBlock *defBlock) - { - ensure(newTemp); - DefUse &defUse = _defUses[newTemp->index]; - Q_ASSERT(!defUse.isValid()); - defUse.temp = *newTemp; - defUse.defStmt = defStmt; - defUse.blockOfStatement = defBlock; - } - - QVector<UntypedTemp> defsUntyped() const - { - QVector<UntypedTemp> res; - res.reserve(tempCount()); - for (const DefUse &du : _defUses) - if (du.isValid()) - res.append(UntypedTemp(du.temp)); - return res; - } - - std::vector<const Temp *> defs() const { - std::vector<const Temp *> res; - const size_t ei = _defUses.size(); - res.reserve(ei); - for (size_t i = 0; i != ei; ++i) { - const DefUse &du = _defUses.at(i); - if (du.isValid()) - res.push_back(&du.temp); - } - return res; - } - - void removeDef(const Temp &variable) { - Q_ASSERT(static_cast<unsigned>(variable.index) < _defUses.size()); - _defUses[variable.index].clear(); - } - - void addUses(const Temp &variable, const QVector<Stmt *> &newUses) - { - Q_ASSERT(static_cast<unsigned>(variable.index) < _defUses.size()); - QVector<Stmt *> &uses = _defUses[variable.index].uses; - for (Stmt *stmt : newUses) - if (std::find(uses.begin(), uses.end(), stmt) == uses.end()) - uses.push_back(stmt); - } - - void addUse(const Temp &variable, Stmt *newUse) - { - if (_defUses.size() <= variable.index) { - _defUses.resize(variable.index + 1); - DefUse &du = _defUses[variable.index]; - du.temp = variable; - du.uses.push_back(newUse); - addUseForStatement(newUse, variable); - return; - } - - QVector<Stmt *> &uses = _defUses[variable.index].uses; - if (std::find(uses.begin(), uses.end(), newUse) == uses.end()) - uses.push_back(newUse); - addUseForStatement(newUse, variable); - } - - int useCount(const Temp &variable) const - { - Q_ASSERT(static_cast<unsigned>(variable.index) < _defUses.size()); - return _defUses[variable.index].uses.size(); - } - - Stmt *defStmt(const Temp &variable) const - { - Q_ASSERT(static_cast<unsigned>(variable.index) < _defUses.size()); - return _defUses[variable.index].defStmt; - } - - BasicBlock *defStmtBlock(const Temp &variable) const - { - Q_ASSERT(static_cast<unsigned>(variable.index) < _defUses.size()); - 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()); - QVector<Stmt *> &uses = _defUses[var.index].uses; - uses.erase(std::remove(uses.begin(), uses.end(), usingStmt), uses.end()); - } - - void registerNewStatement(Stmt *s) - { - ensure(s); - } - - const Temps &usedVars(Stmt *s) const - { - Q_ASSERT(s->id() >= 0); - Q_ASSERT(static_cast<unsigned>(s->id()) < _usesPerStatement.size()); - return _usesPerStatement[s->id()]; - } - - const QVector<Stmt *> &uses(const Temp &var) const - { - return _defUses[var.index].uses; - } - - QVector<Stmt*> removeDefUses(Stmt *s) - { - QVector<Stmt*> defStmts; - for (const Temp &usedVar : usedVars(s)) { - if (Stmt *ds = defStmt(usedVar)) - defStmts += ds; - removeUse(s, usedVar); - } - if (Move *m = s->asMove()) { - if (Temp *t = m->target->asTemp()) - removeDef(*t); - } else if (Phi *p = s->asPhi()) { - removeDef(*p->targetTemp); - } - - return defStmts; - } - - void dump() const - { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - qout << "Defines and uses:" << endl; - for (const DefUse &du : _defUses) { - if (!du.isValid()) - continue; - qout << '%' << du.temp.index; - qout << " -> defined in block " << du.blockOfStatement->index() - << ", statement: " << du.defStmt->id() - << endl; - qout << " uses:"; - for (Stmt *s : du.uses) - qout << ' ' << s->id(); - qout << endl; - } - qout << "Uses per statement:" << endl; - for (size_t i = 0, ei = _usesPerStatement.size(); i != ei; ++i) { - qout << " " << i << ":"; - for (const Temp &t : _usesPerStatement[i]) - qout << ' ' << t.index; - qout << endl; - } - qDebug("%s", buf.data().constData()); - } -}; - -void insertPhiNode(const Temp &a, BasicBlock *y, IR::Function *f) { - Phi *phiNode = f->NewStmt<Phi>(); - phiNode->targetTemp = f->New<Temp>(); - phiNode->targetTemp->init(a.kind, a.index); - y->prependStatement(phiNode); - - phiNode->incoming.resize(y->in.size()); - for (int i = 0, ei = y->in.size(); i < ei; ++i) { - Temp *t = f->New<Temp>(); - t->init(a.kind, a.index); - phiNode->incoming[i] = t; - } -} - -// High-level (recursive) algorithm: -// Mapping: old temp number -> new temp number -// -// Start: -// Rename(start-node) -// -// Rename(node, mapping): -// for each statement S in block n -// if S not in a phi-function -// for each use of some variable x in S -// y = mapping[x] -// replace the use of x with y in S -// for each definition of some variable a in S [1] -// a_new = generate new/unique temp -// mapping[a] = a_new -// replace definition of a with definition of a_new in S -// for each successor Y of block n -// Suppose n is the j-th predecessor of Y -// for each phi function in Y -// suppose the j-th operand of the phi-function is a -// i = mapping[a] -// replace the j-th operand with a_i -// for each child X of n [2] -// Rename(X) -// for each newly generated temp from step [1] restore the old value [3] -// -// This algorithm can run out of CPU stack space when there are lots of basic-blocks, like in a -// switch statement with 8000 cases that all fall-through. The iterativer version below uses a -// work-item stack, where step [1] from the algorithm above also pushes an "undo mapping change", -// and step [2] pushes a "rename(X)" action. This eliminates step [3]. -// -// Iterative version: -// Mapping: old temp number -> new temp number -// -// The stack can hold two kinds of actions: -// "Rename basic block n" -// "Restore count for temp" -// -// Start: -// counter = 0 -// push "Rename start node" onto the stack -// while the stack is not empty: -// take the last item, and process it -// -// Rename(n) = -// for each statement S in block n -// if S not in a phi-function -// for each use of some variable x in S -// y = mapping[x] -// replace the use of x with y in S -// for each definition of some variable a in S -// old = mapping[a] -// push Undo(a, old) -// counter = counter + 1 -// new = counter; -// mapping[a] = new -// replace definition of a with definition of a_new in S -// for each successor Y of block n -// Suppose n is the j-th predecessor of Y -// for each phi function in Y -// suppose the j-th operand of the phi-function is a -// i = mapping[a] -// replace the j-th operand with a_i -// for each child X of n -// push Rename(X) -// -// Undo(t, c) = -// mapping[t] = c -class VariableRenamer -{ - Q_DISABLE_COPY(VariableRenamer) - - IR::Function *function; - DefUses &defUses; - unsigned tempCount; - - typedef std::vector<int> Mapping; // maps from existing/old temp number to the new and unique temp number. - enum { Absent = -1 }; - Mapping vregMapping; - ProcessedBlocks processed; - - BasicBlock *currentBB; - Stmt *currentStmt; - - struct TodoAction { - enum { RestoreVReg, Rename } action; - union { - struct { - unsigned temp; - int previous; - } restoreData; - struct { - BasicBlock *basicBlock; - } renameData; - }; - - bool isValid() const { return action != Rename || renameData.basicBlock != 0; } - - TodoAction() - { - action = Rename; - renameData.basicBlock = 0; - } - - TodoAction(const Temp &t, int prev) - { - Q_ASSERT(t.kind == Temp::VirtualRegister); - - action = RestoreVReg; - restoreData.temp = t.index; - restoreData.previous = prev; - } - - TodoAction(BasicBlock *bb) - { - Q_ASSERT(bb); - - action = Rename; - renameData.basicBlock = bb; - } - }; - - QVector<TodoAction> todo; - -public: - VariableRenamer(IR::Function *f, DefUses &defUses) - : function(f) - , defUses(defUses) - , tempCount(0) - , processed(f) - { - vregMapping.assign(f->tempCount, Absent); - todo.reserve(f->basicBlockCount()); - } - - void run() { - todo.append(TodoAction(function->basicBlock(0))); - - while (!todo.isEmpty()) { - TodoAction todoAction = todo.back(); - Q_ASSERT(todoAction.isValid()); - todo.pop_back(); - - switch (todoAction.action) { - case TodoAction::Rename: - rename(todoAction.renameData.basicBlock); - break; - case TodoAction::RestoreVReg: - restore(vregMapping, todoAction.restoreData.temp, todoAction.restoreData.previous); - break; - default: - Q_UNREACHABLE(); - } - } - - function->tempCount = tempCount; - } - -private: - static inline void restore(Mapping &mapping, int temp, int previous) - { - mapping[temp] = previous; - } - - void rename(BasicBlock *bb) - { - while (bb && !processed.alreadyProcessed(bb)) { - renameStatementsAndPhis(bb); - processed.markAsProcessed(bb); - - BasicBlock *next = 0; - for (BasicBlock *out : bb->out) { - if (processed.alreadyProcessed(out)) - continue; - if (!next) - next = out; - else - todo.append(TodoAction(out)); - } - bb = next; - } - } - - void renameStatementsAndPhis(BasicBlock *bb) - { - currentBB = bb; - - for (Stmt *s : bb->statements()) { - currentStmt = s; - visit(s); - } - - for (BasicBlock *Y : bb->out) { - const int j = Y->in.indexOf(bb); - Q_ASSERT(j >= 0 && j < Y->in.size()); - for (Stmt *s : Y->statements()) { - if (Phi *phi = s->asPhi()) { - Temp *t = phi->incoming[j]->asTemp(); - unsigned newTmp = currentNumber(*t); -// qDebug()<<"I: replacing phi use"<<a<<"with"<<newTmp<<"in L"<<Y->index; - t->index = newTmp; - t->kind = Temp::VirtualRegister; - defUses.addUse(*t, phi); - } else { - break; - } - } - } - } - - unsigned currentNumber(const Temp &t) - { - int nr = Absent; - switch (t.kind) { - case Temp::VirtualRegister: - nr = vregMapping[t.index]; - break; - default: - Q_UNREACHABLE(); - nr = Absent; - break; - } - if (nr == Absent) { - // Special case: we didn't prune the Phi nodes yet, so for proper temps (virtual - // registers) the SSA algorithm might insert superfluous Phis that have uses without - // definition. E.g.: if a temporary got introduced in the "then" clause, it "could" - // reach the "end-if" block, so there will be a phi node for that temp. A later pass - // will clean this up by looking for uses-without-defines in phi nodes. So, what we do - // is to generate a new unique number, and leave it dangling. - nr = nextFreeTemp(t); - } - - return nr; - } - - unsigned nextFreeTemp(const Temp &t) - { - unsigned newIndex = tempCount++; - Q_ASSERT(newIndex <= INT_MAX); - int oldIndex = Absent; - - switch (t.kind) { - case Temp::VirtualRegister: - oldIndex = vregMapping[t.index]; - vregMapping[t.index] = newIndex; - break; - default: - Q_UNREACHABLE(); - } - - todo.append(TodoAction(t, oldIndex)); - - return newIndex; - } - -private: - void visit(Stmt *s) - { - if (auto move = s->asMove()) { - // uses: - visit(move->source); - - // 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); - } - } - - 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 - const int newIdx = nextFreeTemp(*t); -// qDebug()<<"I: replacing def of"<<a<<"with"<<newIdx; - t->kind = Temp::VirtualRegister; - t->index = newIdx; - defUses.addDef(t, currentStmt, currentBB); - } -}; - -// This function converts the IR to semi-pruned SSA form. For details about SSA and the algorightm, -// see [Appel]. For the changes needed for semi-pruned SSA form, and for its advantages, see [Briggs]. -void convertToSSA(IR::Function *function, const DominatorTree &df, DefUses &defUses) -{ - // Collect all applicable variables: - VariableCollector variables(function); - - // Prepare for phi node insertion: - std::vector<BitVector > A_phi; - const size_t ei = function->basicBlockCount(); - A_phi.resize(ei); - for (size_t i = 0; i != ei; ++i) - A_phi[i].assign(function->tempCount, false); - - std::vector<BasicBlock *> W; - W.reserve(8); - - // Place phi functions: - for (const Temp &a : variables.allTemps()) { - if (a.isInvalid()) - continue; - if (!variables.isNonLocal(a)) - continue; // for semi-pruned SSA - - W.clear(); - variables.collectDefSites(a, W); - while (!W.empty()) { - BasicBlock *n = W.back(); - W.pop_back(); - const BasicBlockSet &dominatorFrontierForN = df.dominatorFrontier(n); - for (BasicBlockSet::const_iterator it = dominatorFrontierForN.begin(), eit = dominatorFrontierForN.end(); - it != eit; ++it) { - BasicBlock *y = *it; - if (!A_phi.at(y->index()).at(a.index)) { - insertPhiNode(a, y, function); - A_phi[y->index()].setBit(a.index); - const std::vector<int> &varsInBlockY = variables.inBlock(y); - if (std::find(varsInBlockY.begin(), varsInBlockY.end(), a.index) == varsInBlockY.end()) - W.push_back(y); - } - } - } - } - - // Rename variables: - VariableRenamer(function, defUses).run(); -} - -/// Calculate if a phi node result is used only by other phi nodes, and if those uses are -/// in turn also used by other phi nodes. -bool hasPhiOnlyUses(Phi *phi, const DefUses &defUses, QBitArray &collectedPhis) -{ - collectedPhis.setBit(phi->id()); - - for (Stmt *use : defUses.uses(*phi->targetTemp)) { - Phi *dependentPhi = use->asPhi(); - if (!dependentPhi) - return false; // there is a use by a non-phi node - - if (collectedPhis.at(dependentPhi->id())) - continue; // we already found this node - - if (!hasPhiOnlyUses(dependentPhi, defUses, collectedPhis)) - return false; - } - - return true; -} - -void cleanupPhis(DefUses &defUses) -{ - QBitArray toRemove(defUses.statementCount()); - QBitArray collectedPhis(defUses.statementCount()); - std::vector<Phi *> allPhis; - allPhis.reserve(32); - - for (const Temp *def : defUses.defs()) { - Stmt *defStmt = defUses.defStmt(*def); - if (!defStmt) - continue; - - Phi *phi = defStmt->asPhi(); - if (!phi) - continue; - allPhis.push_back(phi); - if (toRemove.at(phi->id())) - continue; - - collectedPhis.fill(false); - if (hasPhiOnlyUses(phi, defUses, collectedPhis)) - toRemove |= collectedPhis; - } - - for (Phi *phi : allPhis) { - if (!toRemove.at(phi->id())) - continue; - - const Temp &targetVar = *phi->targetTemp; - defUses.defStmtBlock(targetVar)->removeStatement(phi); - - for (const Temp &usedVar : defUses.usedVars(phi)) - defUses.removeUse(phi, usedVar); - defUses.removeDef(targetVar); - } - - defUses.cleanup(); -} - -class StatementWorklist -{ - IR::Function *theFunction; - std::vector<Stmt *> stmts; - BitVector worklist; - unsigned worklistSize; - std::vector<int> replaced; - BitVector removed; - - Q_DISABLE_COPY(StatementWorklist) - -public: - StatementWorklist(IR::Function *function) - : theFunction(function) - , stmts(function->statementCount(), static_cast<Stmt *>(0)) - , worklist(function->statementCount(), false) - , worklistSize(0) - , replaced(function->statementCount(), Stmt::InvalidId) - , removed(function->statementCount()) - { - grow(); - - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) - continue; - - for (Stmt *s : bb->statements()) { - if (!s) - continue; - - stmts[s->id()] = s; - worklist.setBit(s->id()); - ++worklistSize; - } - } - } - - void reset() - { - worklist.assign(worklist.size(), false); - worklistSize = 0; - - for (Stmt *s : stmts) { - if (!s) - continue; - - worklist.setBit(s->id()); - ++worklistSize; - } - - replaced.assign(replaced.size(), Stmt::InvalidId); - removed.assign(removed.size(), false); - } - - void remove(Stmt *stmt) - { - replaced[stmt->id()] = Stmt::InvalidId; - removed.setBit(stmt->id()); - if (worklist.at(stmt->id())) { - worklist.clearBit(stmt->id()); - Q_ASSERT(worklistSize > 0); - --worklistSize; - } - } - - void replace(Stmt *oldStmt, Stmt *newStmt) - { - Q_ASSERT(oldStmt); - Q_ASSERT(replaced[oldStmt->id()] == Stmt::InvalidId); - Q_ASSERT(removed.at(oldStmt->id()) == false); - - Q_ASSERT(newStmt); - registerNewStatement(newStmt); - Q_ASSERT(replaced[newStmt->id()] == Stmt::InvalidId); - Q_ASSERT(removed.at(newStmt->id()) == false); - - replaced[oldStmt->id()] = newStmt->id(); - worklist.clearBit(oldStmt->id()); - } - - void applyToFunction() - { - for (BasicBlock *bb : theFunction->basicBlocks()) { - if (bb->isRemoved()) - continue; - - for (int i = 0; i < bb->statementCount();) { - Stmt *stmt = bb->statements().at(i); - - int id = stmt->id(); - Q_ASSERT(id != Stmt::InvalidId); - Q_ASSERT(static_cast<unsigned>(stmt->id()) < stmts.size()); - - for (int replacementId = replaced[id]; replacementId != Stmt::InvalidId; replacementId = replaced[replacementId]) - id = replacementId; - Q_ASSERT(id != Stmt::InvalidId); - Q_ASSERT(static_cast<unsigned>(stmt->id()) < stmts.size()); - - if (removed.at(id)) { - bb->removeStatement(i); - } else { - if (id != stmt->id()) - bb->replaceStatement(i, stmts[id]); - - ++i; - } - } - } - - replaced.assign(replaced.size(), Stmt::InvalidId); - removed.assign(removed.size(), false); - } - - StatementWorklist &operator+=(const QVector<Stmt *> &stmts) - { - for (Stmt *s : stmts) - this->operator+=(s); - - return *this; - } - - StatementWorklist &operator+=(Stmt *s) - { - if (!s) - return *this; - - Q_ASSERT(s->id() >= 0); - Q_ASSERT(s->id() < worklist.size()); - - if (!worklist.at(s->id())) { - worklist.setBit(s->id()); - ++worklistSize; - } - - return *this; - } - - StatementWorklist &operator-=(Stmt *s) - { - Q_ASSERT(s->id() >= 0); - Q_ASSERT(s->id() < worklist.size()); - - if (worklist.at(s->id())) { - worklist.clearBit(s->id()); - Q_ASSERT(worklistSize > 0); - --worklistSize; - } - - return *this; - } - - unsigned size() const - { - return worklistSize; - } - - Stmt *takeNext(Stmt *last) - { - if (worklistSize == 0) - return 0; - - const int startAt = last ? last->id() + 1 : 0; - Q_ASSERT(startAt >= 0); - Q_ASSERT(startAt <= worklist.size()); - - Q_ASSERT(static_cast<size_t>(worklist.size()) == stmts.size()); - - int pos = worklist.findNext(startAt, true, /*wrapAround = */true); - - worklist.clearBit(pos); - Q_ASSERT(worklistSize > 0); - --worklistSize; - Stmt *s = stmts.at(pos); - Q_ASSERT(s); - - if (removed.at(s->id())) - return takeNext(s); - - return s; - } - - IR::Function *function() const - { - return theFunction; - } - - void registerNewStatement(Stmt *s) - { - Q_ASSERT(s->id() >= 0); - if (static_cast<unsigned>(s->id()) >= stmts.size()) { - if (static_cast<unsigned>(s->id()) >= stmts.capacity()) - grow(); - - int newSize = s->id() + 1; - stmts.resize(newSize, 0); - worklist.resize(newSize); - replaced.resize(newSize, Stmt::InvalidId); - removed.resize(newSize); - } - - stmts[s->id()] = s; - } - -private: - void grow() - { - Q_ASSERT(stmts.capacity() < INT_MAX / 2); - int newCapacity = ((static_cast<int>(stmts.capacity()) + 1) * 3) / 2; - stmts.reserve(newCapacity); - worklist.reserve(newCapacity); - replaced.reserve(newCapacity); - removed.reserve(newCapacity); - } -}; - -class SideEffectsChecker -{ - bool _sideEffect; - -public: - SideEffectsChecker() - : _sideEffect(false) - {} - - ~SideEffectsChecker() - {} - - bool hasSideEffects(Expr *expr) - { - bool sideEffect = false; - qSwap(_sideEffect, sideEffect); - visit(expr); - qSwap(_sideEffect, sideEffect); - return sideEffect; - } - -protected: - void markAsSideEffect() - { - _sideEffect = true; - } - - bool seenSideEffects() const { return _sideEffect; } - - 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); - } - } - - 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 - // a side-effect. - if (e->builtin == Name::builtin_invalid || (e->id && *e->id != QLatin1String("this"))) - markAsSideEffect(); - } - - void visitClosure(Closure *) { - markAsSideEffect(); - } - - void visitConvert(Convert *e) { - visit(e->expr); - - switch (e->expr->type) { - case QObjectType: - case StringType: - case VarType: - markAsSideEffect(); - break; - default: - break; - } - } - - void visitUnop(Unop *e) { - visit(e->expr); - - switch (e->op) { - case OpUPlus: - case OpUMinus: - case OpNot: - case OpIncrement: - case OpDecrement: - if (e->expr->type == VarType || e->expr->type == StringType || e->expr->type == QObjectType) - markAsSideEffect(); - break; - - default: - break; - } - } - - 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. - _sideEffect = hasSideEffects(e->left); - _sideEffect |= hasSideEffects(e->right); - - if (e->left->type == VarType || e->left->type == StringType || e->left->type == QObjectType - || e->right->type == VarType || e->right->type == StringType || e->right->type == QObjectType) - markAsSideEffect(); - } - - void visitSubscript(Subscript *e) { - visit(e->base); - visit(e->index); - markAsSideEffect(); - } - - void visitMember(Member *e) { - visit(e->base); - if (e->freeOfSideEffects) - return; - markAsSideEffect(); - } - - void visitCall(Call *e) { - visit(e->base); - for (ExprList *args = e->args; args; args = args->next) - visit(args->expr); - markAsSideEffect(); // TODO: there are built-in functions that have no side effect. - } - - void visitNew(New *e) { - visit(e->base); - for (ExprList *args = e->args; args; args = args->next) - visit(args->expr); - markAsSideEffect(); // TODO: there are built-in types that have no side effect. - } -}; - -class EliminateDeadCode: public SideEffectsChecker -{ - DefUses &_defUses; - StatementWorklist &_worklist; - QVarLengthArray<Temp *, 8> _collectedTemps; - -public: - EliminateDeadCode(DefUses &defUses, StatementWorklist &worklist) - : _defUses(defUses) - , _worklist(worklist) - {} - - void run(Expr *&expr, Stmt *stmt) { - _collectedTemps.clear(); - if (!hasSideEffects(expr)) { - expr = 0; - for (Temp *t : _collectedTemps) { - _defUses.removeUse(stmt, *t); - _worklist += _defUses.defStmt(*t); - } - } - } - -protected: - void visitTemp(Temp *e) Q_DECL_OVERRIDE Q_DECL_FINAL - { - _collectedTemps.append(e); - } -}; - -class PropagateTempTypes -{ - const DefUses &defUses; - UntypedTemp theTemp; - DiscoveredType newType; - -public: - PropagateTempTypes(const DefUses &defUses) - : defUses(defUses) - {} - - void run(const UntypedTemp &temp, const DiscoveredType &type) - { - newType = type; - theTemp = temp; - if (Stmt *defStmt = defUses.defStmt(temp.temp)) - visit(defStmt); - for (Stmt *use : defUses.uses(temp.temp)) - visit(use); - } - -private: - void visit(Stmt *s) - { - STMT_VISIT_ALL_KINDS(s); - } - - 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 -{ - enum { DebugTypeInference = 0 }; - - QQmlEnginePrivate *qmlEngine; - const DefUses &_defUses; - typedef std::vector<DiscoveredType> TempTypes; - TempTypes _tempTypes; - StatementWorklist *_worklist; - struct TypingResult { - DiscoveredType type; - bool fullyTyped; - - TypingResult(const DiscoveredType &type = DiscoveredType()) { -#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 6 - // avoid optimization bug in gcc 4.6.3 armhf - ((int volatile &) this->type.type) = type.type; -#endif - this->type = type; - fullyTyped = type.type != UnknownType; - } - explicit TypingResult(MemberExpressionResolver *memberResolver) - : type(memberResolver) - , fullyTyped(true) - {} - }; - TypingResult _ty; - Stmt *_currentStmt; - -public: - TypeInference(QQmlEnginePrivate *qmlEngine, const DefUses &defUses) - : qmlEngine(qmlEngine) - , _defUses(defUses) - , _tempTypes(_defUses.tempCount()) - , _worklist(0) - , _ty(UnknownType) - , _currentStmt(nullptr) - {} - - void run(StatementWorklist &w) { - _worklist = &w; - - Stmt *s = 0; - while ((s = _worklist->takeNext(s))) { - if (s->asJump()) - continue; - - if (DebugTypeInference) { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - qout<<"Typing stmt "; - IRPrinter(&qout).print(s); - qout.flush(); - qDebug("%s", buf.data().constData()); - - qDebug("%u left in the worklist", _worklist->size()); - } - - if (!run(s)) { - *_worklist += s; - if (DebugTypeInference) { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - qout<<"Pushing back stmt: "; - IRPrinter(&qout).print(s); - qout.flush(); - qDebug("%s", buf.data().constData()); - } - } else { - if (DebugTypeInference) { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - qout<<"Finished: "; - IRPrinter(&qout).print(s); - qout.flush(); - qDebug("%s", buf.data().constData()); - } - } - } - - PropagateTempTypes propagator(_defUses); - for (size_t i = 0, ei = _tempTypes.size(); i != ei; ++i) { - const Temp &temp = _defUses.temp(int(i)); - if (temp.kind == Temp::Invalid) - continue; - const DiscoveredType &tempType = _tempTypes[i]; - if (tempType.type == UnknownType) - continue; - propagator.run(temp, tempType); - } - - _worklist = 0; - } - -private: - bool run(Stmt *s) { - TypingResult ty; - std::swap(_ty, ty); - std::swap(_currentStmt, s); - visit(_currentStmt); - std::swap(_currentStmt, s); - std::swap(_ty, ty); - return ty.fullyTyped; - } - - TypingResult run(Expr *e) { - TypingResult ty; - std::swap(_ty, ty); - visit(e); - std::swap(_ty, ty); - - if (ty.type != UnknownType) - setType(e, ty.type); - return ty; - } - - void setType(Expr *e, DiscoveredType ty) { - if (Temp *t = e->asTemp()) { - if (DebugTypeInference) - qDebug() << "Setting type for temp" << t->index - << " to " << typeName(Type(ty.type)) << "(" << ty.type << ")" - << endl; - - DiscoveredType &it = _tempTypes[t->index]; - if (it != ty) { - it = ty; - - if (DebugTypeInference) { - for (Stmt *s : _defUses.uses(*t)) { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - qout << "Pushing back dependent stmt: "; - IRPrinter(&qout).print(s); - qout.flush(); - qDebug("%s", buf.data().constData()); - } - } - - for (Stmt *s : qAsConst(_defUses.uses(*t))) { - if (s != _currentStmt) { - *_worklist += s; - } - } - } - } else { - e->type = (Type) ty.type; - } - } - -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(c->value)) - _ty = TypingResult(UInt32Type); - else - _ty = TypingResult(c->type); - } else - _ty = TypingResult(c->type); - } - 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); - } - void visitArgLocal(ArgLocal *e) { - _ty = TypingResult(VarType); - setType(e, _ty.type); - } - - void visitClosure(Closure *) { _ty = TypingResult(VarType); } - void visitConvert(Convert *e) { - _ty = TypingResult(e->type); - } - - void visitUnop(Unop *e) { - _ty = run(e->expr); - switch (e->op) { - case OpUPlus: _ty.type = DoubleType; return; - case OpUMinus: _ty.type = DoubleType; return; - case OpCompl: _ty.type = SInt32Type; return; - case OpNot: _ty.type = BoolType; return; - - case OpIncrement: - case OpDecrement: - Q_ASSERT(!"Inplace operators should have been removed!"); - Q_UNREACHABLE(); - default: - Q_UNIMPLEMENTED(); - Q_UNREACHABLE(); - } - } - - void visitBinop(Binop *e) { - TypingResult leftTy = run(e->left); - TypingResult rightTy = run(e->right); - _ty.fullyTyped = leftTy.fullyTyped && rightTy.fullyTyped; - - switch (e->op) { - case OpAdd: - if (leftTy.type.test(VarType) || leftTy.type.test(QObjectType) || rightTy.type.test(VarType) || rightTy.type.test(QObjectType)) - _ty.type = VarType; - else if (leftTy.type.test(StringType) || rightTy.type.test(StringType)) - _ty.type = StringType; - else if (leftTy.type != UnknownType && rightTy.type != UnknownType) - _ty.type = DoubleType; - else - _ty.type = UnknownType; - break; - case OpSub: - _ty.type = DoubleType; - break; - - case OpMul: - case OpDiv: - case OpMod: - _ty.type = DoubleType; - break; - - case OpBitAnd: - case OpBitOr: - case OpBitXor: - case OpLShift: - case OpRShift: - _ty.type = SInt32Type; - break; - case OpURShift: - _ty.type = UInt32Type; - break; - - case OpGt: - case OpLt: - case OpGe: - case OpLe: - case OpEqual: - case OpNotEqual: - case OpStrictEqual: - case OpStrictNotEqual: - case OpAnd: - case OpOr: - case OpInstanceof: - case OpIn: - _ty.type = BoolType; - break; - - default: - Q_UNIMPLEMENTED(); - Q_UNREACHABLE(); - } - } - - 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; - } - 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; - } - void visitSubscript(Subscript *e) { - _ty.fullyTyped = run(e->base).fullyTyped && run(e->index).fullyTyped; - _ty.type = VarType; - } - - void visitMember(Member *e) { - _ty = run(e->base); - - if (_ty.fullyTyped && _ty.type.memberResolver && _ty.type.memberResolver->isValid()) { - MemberExpressionResolver *resolver = _ty.type.memberResolver; - _ty.type = resolver->resolveMember(qmlEngine, resolver, e); - } else - _ty.type = VarType; - } - - 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) { - _ty = TypingResult(t->memberResolver); - setType(n, _ty.type); - setType(t, _ty.type); - return; - } - } - TypingResult sourceTy = run(s->source); - setType(t, sourceTy.type); - _ty = sourceTy; - return; - } - - TypingResult sourceTy = run(s->source); - _ty = run(s->target); - _ty.fullyTyped &= sourceTy.fullyTyped; - } - - 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]); - if (!ty.fullyTyped && _ty.fullyTyped) { - // When one of the temps not fully typed, we already know that we cannot completely type this node. - // So, pick the type we calculated upto this point, and wait until the unknown one will be typed. - // At that point, this statement will be re-scheduled, and then we can fully type this node. - _ty.fullyTyped = false; - break; - } - _ty.type.type |= ty.type.type; - _ty.fullyTyped &= ty.fullyTyped; - if (_ty.type.test(QObjectType) && _ty.type.memberResolver) - _ty.type.memberResolver->clear(); // ### TODO: find common ancestor meta-object - } - - switch (_ty.type.type) { - case UnknownType: - case UndefinedType: - case NullType: - case BoolType: - case SInt32Type: - case UInt32Type: - case DoubleType: - case StringType: - case QObjectType: - case VarType: - // The type is not a combination of two or more types, so we're done. - break; - - default: - // There are multiple types involved, so: - if (_ty.type.isNumber()) - // The type is any combination of double/int32/uint32, but nothing else. So we can - // type it as double. - _ty.type = DoubleType; - else - // There just is no single type that can hold this combination, so: - _ty.type = VarType; - } - - setType(s->targetTemp, _ty.type); - } -}; - -class ReverseInference -{ - const DefUses &_defUses; - -public: - ReverseInference(const DefUses &defUses) - : _defUses(defUses) - {} - - void run(IR::Function *f) - { - Q_UNUSED(f); - - QVector<UntypedTemp> knownOk; - QVector<UntypedTemp> candidates = _defUses.defsUntyped(); - while (!candidates.isEmpty()) { - UntypedTemp temp = candidates.last(); - candidates.removeLast(); - - if (knownOk.contains(temp)) - continue; - - if (!isUsedAsInt32(temp, knownOk)) - continue; - - Stmt *s = _defUses.defStmt(temp.temp); - Move *m = s->asMove(); - if (!m) - continue; - Temp *target = m->target->asTemp(); - if (!target || temp != UntypedTemp(*target) || target->type == SInt32Type) - continue; - if (Temp *t = m->source->asTemp()) { - candidates.append(*t); - } else if (m->source->asConvert()) { - break; - } else if (Binop *b = m->source->asBinop()) { - bool iterateOnOperands = true; - - switch (b->op) { - case OpSub: - case OpMul: - case OpAdd: - if (b->left->type == SInt32Type && b->right->type == SInt32Type) { - iterateOnOperands = false; - break; - } else { - continue; - } - case OpBitAnd: - case OpBitOr: - case OpBitXor: - case OpLShift: - case OpRShift: - case OpURShift: - break; - default: - continue; - } - - if (iterateOnOperands) { - if (Temp *lt = b->left->asTemp()) - candidates.append(*lt); - if (Temp *rt = b->right->asTemp()) - candidates.append(*rt); - } - } else if (Unop *u = m->source->asUnop()) { - if (u->op == OpCompl || u->op == OpUPlus) { - if (Temp *t = u->expr->asTemp()) - candidates.append(*t); - } - } else { - continue; - } - - knownOk.append(temp); - } - - PropagateTempTypes propagator(_defUses); - for (const UntypedTemp &t : qAsConst(knownOk)) { - propagator.run(t, SInt32Type); - if (Stmt *defStmt = _defUses.defStmt(t.temp)) { - if (Move *m = defStmt->asMove()) { - if (Convert *c = m->source->asConvert()) { - c->type = SInt32Type; - } else if (Unop *u = m->source->asUnop()) { - if (u->op != OpUMinus) - u->type = SInt32Type; - } else if (Binop *b = m->source->asBinop()) { - b->type = SInt32Type; - } - } - } - } - } - -private: - bool isUsedAsInt32(const UntypedTemp &t, const QVector<UntypedTemp> &knownOk) const - { - const QVector<Stmt *> &uses = _defUses.uses(t.temp); - if (uses.isEmpty()) - return false; - - for (Stmt *use : uses) { - if (Move *m = use->asMove()) { - Temp *targetTemp = m->target->asTemp(); - - if (m->source->asTemp()) { - if (!targetTemp || !knownOk.contains(*targetTemp)) - return false; - } else if (m->source->asConvert()) { - continue; - } else if (Binop *b = m->source->asBinop()) { - switch (b->op) { - case OpAdd: - case OpSub: - case OpMul: - if (!targetTemp || !knownOk.contains(*targetTemp)) - return false; - Q_FALLTHROUGH(); - case OpBitAnd: - case OpBitOr: - case OpBitXor: - case OpRShift: - case OpLShift: - case OpURShift: - continue; - default: - return false; - } - } else if (Unop *u = m->source->asUnop()) { - if (u->op == OpUPlus) { - if (!targetTemp || !knownOk.contains(*targetTemp)) - return false; - } else if (u->op != OpCompl) { - return false; - } - } else { - return false; - } - } else - return false; - } - - return true; - } -}; - -void convertConst(Const *c, Type targetType) -{ - switch (targetType) { - case DoubleType: - break; - case SInt32Type: - c->value = QV4::Primitive::toInt32(c->value); - break; - case UInt32Type: - c->value = QV4::Primitive::toUInt32(c->value); - break; - case BoolType: - c->value = !(c->value == 0 || std::isnan(c->value)); - break; - case NullType: - case UndefinedType: - c->value = qt_qnan(); - c->type = targetType; - break; - default: - Q_UNIMPLEMENTED(); - Q_ASSERT(!"Unimplemented!"); - break; - } - c->type = targetType; -} - -class TypePropagation -{ - DefUses &_defUses; - Type _ty; - IR::Function *_f; - - bool run(Expr *&e, Type requestedType = UnknownType, bool insertConversion = true) { - qSwap(_ty, requestedType); - visit(e); - qSwap(_ty, requestedType); - - if (requestedType != UnknownType) { - if (e->type != requestedType) { - if (requestedType & NumberType || requestedType == BoolType) { - if (insertConversion) - addConversion(e, requestedType); - return true; - } - } - } - - return false; - } - - struct Conversion { - Expr **expr; - Type targetType; - Stmt *stmt; - - Conversion(Expr **expr = 0, Type targetType = UnknownType, Stmt *stmt = 0) - : expr(expr) - , targetType(targetType) - , stmt(stmt) - {} - }; - - Stmt *_currStmt; - QVector<Conversion> _conversions; - - void addConversion(Expr *&expr, Type targetType) { - _conversions.append(Conversion(&expr, targetType, _currStmt)); - } - -public: - TypePropagation(DefUses &defUses) : _defUses(defUses), _ty(UnknownType) {} - - void run(IR::Function *f, StatementWorklist &worklist) { - _f = f; - for (BasicBlock *bb : f->basicBlocks()) { - if (bb->isRemoved()) - continue; - _conversions.clear(); - - for (Stmt *s : bb->statements()) { - _currStmt = s; - visit(s); - } - - for (const Conversion &conversion : qAsConst(_conversions)) { - IR::Move *move = conversion.stmt->asMove(); - - // Note: isel only supports move into member when source is a temp, so convert - // is not a supported source. - if (move && move->source->asTemp() && !move->target->asMember()) { - *conversion.expr = bb->CONVERT(*conversion.expr, conversion.targetType); - } else if (Const *c = (*conversion.expr)->asConst()) { - convertConst(c, conversion.targetType); - } else if (ArgLocal *al = (*conversion.expr)->asArgLocal()) { - Temp *target = bb->TEMP(bb->newTemp()); - target->type = conversion.targetType; - Expr *convert = bb->CONVERT(al, conversion.targetType); - Move *convCall = f->NewStmt<Move>(); - worklist.registerNewStatement(convCall); - convCall->init(target, convert); - _defUses.addDef(target, convCall, bb); - - Temp *source = bb->TEMP(target->index); - source->type = conversion.targetType; - _defUses.addUse(*source, conversion.stmt); - - if (conversion.stmt->asPhi()) { - // Only temps can be used as arguments to phi nodes, so this is a sanity check...: - Q_UNREACHABLE(); - } else { - bb->insertStatementBefore(conversion.stmt, convCall); - } - - *conversion.expr = source; - } else if (Temp *t = (*conversion.expr)->asTemp()) { - Temp *target = bb->TEMP(bb->newTemp()); - target->type = conversion.targetType; - Expr *convert = bb->CONVERT(t, conversion.targetType); - Move *convCall = f->NewStmt<Move>(); - worklist.registerNewStatement(convCall); - convCall->init(target, convert); - _defUses.addDef(target, convCall, bb); - _defUses.addUse(*t, convCall); - - Temp *source = bb->TEMP(target->index); - source->type = conversion.targetType; - _defUses.removeUse(conversion.stmt, *t); - _defUses.addUse(*source, conversion.stmt); - - if (Phi *phi = conversion.stmt->asPhi()) { - int idx = phi->incoming.indexOf(t); - Q_ASSERT(idx != -1); - bb->in[idx]->insertStatementBeforeTerminator(convCall); - } else { - bb->insertStatementBefore(conversion.stmt, convCall); - } - - *conversion.expr = source; - } else if (Unop *u = (*conversion.expr)->asUnop()) { - // convert: - // int32{%2} = double{-double{%1}}; - // to: - // double{%3} = double{-double{%1}}; - // int32{%2} = int32{convert(double{%3})}; - Temp *tmp = bb->TEMP(bb->newTemp()); - tmp->type = u->type; - Move *extraMove = f->NewStmt<Move>(); - worklist.registerNewStatement(extraMove); - extraMove->init(tmp, u); - _defUses.addDef(tmp, extraMove, bb); - - if (Temp *unopOperand = u->expr->asTemp()) { - _defUses.addUse(*unopOperand, extraMove); - _defUses.removeUse(move, *unopOperand); - } - - bb->insertStatementBefore(conversion.stmt, extraMove); - - *conversion.expr = bb->CONVERT(CloneExpr::cloneTemp(tmp, f), conversion.targetType); - _defUses.addUse(*tmp, move); - } else { - Q_UNREACHABLE(); - } - } - } - } - -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); - else if (_ty == UInt32Type) - c->value = QV4::Primitive::toUInt32(c->value); - c->type = _ty; - } - } - - void visitBinop(Binop *e) { - // FIXME: This routine needs more tuning! - switch (e->op) { - case OpAdd: - case OpSub: - case OpMul: - case OpDiv: - case OpMod: - case OpBitAnd: - case OpBitOr: - case OpBitXor: - run(e->left, e->type); - run(e->right, e->type); - break; - - case OpLShift: - case OpRShift: - case OpURShift: - run(e->left, SInt32Type); - run(e->right, SInt32Type); - break; - - case OpGt: - case OpLt: - case OpGe: - case OpLe: - case OpEqual: - case OpNotEqual: - if (e->left->type == DoubleType) { - run(e->right, DoubleType); - } else if (e->right->type == DoubleType) { - run(e->left, DoubleType); - } else { - run(e->left, e->left->type); - run(e->right, e->right->type); - } - break; - - case OpStrictEqual: - case OpStrictNotEqual: - case OpInstanceof: - case OpIn: - run(e->left, e->left->type); - run(e->right, e->right->type); - break; - - default: - Q_UNIMPLEMENTED(); - Q_UNREACHABLE(); - } - } - void visitCall(Call *e) { - run(e->base); - for (ExprList *it = e->args; it; it = it->next) - run(it->expr); - } - void visitNew(New *e) { - run(e->base); - for (ExprList *it = e->args; it; it = it->next) - run(it->expr); - } - 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 - - run(s->target); - - if (Unop *u = s->source->asUnop()) { - if (u->op == OpUPlus) { - if (run(u->expr, s->target->type, false)) { - Convert *convert = _f->New<Convert>(); - convert->init(u->expr, s->target->type); - s->source = convert; - } else { - s->source = u->expr; - } - - return; - } - } - - const Member *targetMember = s->target->asMember(); - const bool inhibitConversion = targetMember && targetMember->inhibitTypeConversionOnWrite; - - run(s->source, s->target->type, !inhibitConversion); - } - void visitCJump(CJump *s) { - run(s->cond, BoolType); - } - 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); - } -}; - -void splitCriticalEdges(IR::Function *f, DominatorTree &df, StatementWorklist &worklist, DefUses &defUses) -{ - const QVector<BasicBlock *> copy = f->basicBlocks(); - for (BasicBlock *toBB : copy) { - if (toBB->isRemoved()) - continue; - if (toBB->in.size() < 2) - continue; - - for (int inIdx = 0, eInIdx = toBB->in.size(); inIdx != eInIdx; ++inIdx) { - BasicBlock *fromBB = toBB->in[inIdx]; - if (fromBB->out.size() < 2) - continue; - - // We found a critical edge. - // create the basic block: - BasicBlock *newBB = f->newBasicBlock(toBB->catchBlock); - Jump *s = f->NewStmt<Jump>(); - worklist.registerNewStatement(s); - defUses.registerNewStatement(s); - s->init(toBB); - newBB->appendStatement(s); - - // rewire the old outgoing edge - int outIdx = fromBB->out.indexOf(toBB); - fromBB->out[outIdx] = newBB; - newBB->in.append(fromBB); - - // rewire the old incoming edge - toBB->in[inIdx] = newBB; - newBB->out.append(toBB); - - // add newBB to the correct loop group - if (toBB->isGroupStart()) { - if (fromBB == toBB) { - // special case: the loop header points back to itself (so it's a small loop). - newBB->setContainingGroup(toBB); - } else { - BasicBlock *container; - for (container = fromBB->containingGroup(); container; container = container->containingGroup()) - if (container == toBB) - break; - if (container == toBB) // if we were already inside the toBB loop - newBB->setContainingGroup(toBB); - else - newBB->setContainingGroup(toBB->containingGroup()); - } - } else { - newBB->setContainingGroup(toBB->containingGroup()); - } - - // patch the terminator - Stmt *terminator = fromBB->terminator(); - if (Jump *j = terminator->asJump()) { - Q_ASSERT(outIdx == 0); - j->target = newBB; - } else if (CJump *j = terminator->asCJump()) { - if (outIdx == 0) - j->iftrue = newBB; - else if (outIdx == 1) - j->iffalse = newBB; - else - Q_ASSERT(!"Invalid out edge index for CJUMP!"); - } else if (terminator->asRet()) { - Q_ASSERT(!"A block with a RET at the end cannot have outgoing edges."); - } else { - Q_ASSERT(!"Unknown terminator!"); - } - -// qDebug() << "splitting edge" << fromBB->index() << "->" << toBB->index() -// << "by inserting block" << newBB->index(); - - // Set the immediate dominator of the new block to inBB - df.setImmediateDominator(newBB, fromBB); - - bool toNeedsNewIdom = true; - for (BasicBlock *bb : toBB->in) { - if (bb != newBB && bb != toBB && !df.dominates(toBB, bb)) { - toNeedsNewIdom = false; - break; - } - } - if (toNeedsNewIdom) - df.setImmediateDominator(toBB, newBB); - } - } -} - -// Detect all (sub-)loops in a function. -// -// Doing loop detection on the CFG is better than relying on the statement information in -// order to mark loops. Although JavaScript only has natural loops, it can still be the case -// that something is not a loop even though a loop-like-statement is in the source. For -// example: -// while (true) { -// if (i > 0) -// break; -// else -// break; -// } -// -// Algorithm: -// - do a DFS on the dominator tree, where for each node: -// - collect all back-edges -// - if there are back-edges, the node is a loop-header for a new loop, so: -// - walk the CFG is reverse-direction, and for every node: -// - if the node already belongs to a loop, we've found a nested loop: -// - get the loop-header for the (outermost) nested loop -// - add that loop-header to the current loop -// - continue by walking all incoming edges that do not yet belong to the current loop -// - if the node does not belong to a loop yet, add it to the current loop, and -// go on with all incoming edges -// -// Loop-header detection by checking for back-edges is very straight forward: a back-edge is -// an incoming edge where the other node is dominated by the current node. Meaning: all -// execution paths that reach that other node have to go through the current node, that other -// node ends with a (conditional) jump back to the loop header. -// -// The exact order of the DFS on the dominator tree is not important. The only property has to -// be that a node is only visited when all the nodes it dominates have been visited before. -// The reason for the DFS is that for nested loops, the inner loop's loop-header is dominated -// by the outer loop's header. So, by visiting depth-first, sub-loops are identified before -// their containing loops, which makes nested-loop identification free. An added benefit is -// that the nodes for those sub-loops are only processed once. -// -// Note: independent loops that share the same header are merged together. For example, in -// the code snippet below, there are 2 back-edges into the loop-header, but only one single -// loop will be detected. -// while (a) { -// if (b) -// continue; -// else -// continue; -// } -class LoopDetection -{ - enum { DebugLoopDetection = 0 }; - - Q_DISABLE_COPY(LoopDetection) - -public: - struct LoopInfo - { - BasicBlock *loopHeader; - QVector<BasicBlock *> loopBody; - QVector<LoopInfo *> nestedLoops; - LoopInfo *parentLoop; - - LoopInfo(BasicBlock *loopHeader = 0) - : loopHeader(loopHeader) - , parentLoop(0) - {} - - bool isValid() const - { return loopHeader != 0; } - - void addNestedLoop(LoopInfo *nested) - { - Q_ASSERT(nested); - Q_ASSERT(!nestedLoops.contains(nested)); - Q_ASSERT(nested->parentLoop == 0); - nested->parentLoop = this; - nestedLoops.append(nested); - } - }; - -public: - LoopDetection(const DominatorTree &dt) - : dt(dt) - {} - - ~LoopDetection() - { - qDeleteAll(loopInfos); - } - - void run(IR::Function *function) - { - std::vector<BasicBlock *> backedges; - backedges.reserve(4); - - const auto order = dt.calculateDFNodeIterOrder(); - for (BasicBlock *bb : order) { - Q_ASSERT(!bb->isRemoved()); - - backedges.clear(); - - for (BasicBlock *in : bb->in) - if (bb == in || dt.dominates(bb, in)) - backedges.push_back(in); - - if (!backedges.empty()) { - subLoop(bb, backedges); - } - } - - createLoopInfos(function); - dumpLoopInfo(); - } - - void dumpLoopInfo() const - { - if (!DebugLoopDetection) - return; - - qDebug() << "Found" << loopInfos.size() << "loops"; - for (const LoopInfo *info : loopInfos) { - qDebug() << "Loop header:" << info->loopHeader->index() - << "for loop" << quint64(info); - for (BasicBlock *bb : info->loopBody) - qDebug() << " " << bb->index(); - for (LoopInfo *nested : info->nestedLoops) - qDebug() << " sub loop:" << quint64(nested); - qDebug() << " parent loop:" << quint64(info->parentLoop); - } - } - - QVector<LoopInfo *> allLoops() const - { return loopInfos; } - - // returns all loop headers for loops that have no nested loops. - QVector<LoopInfo *> innermostLoops() const - { - QVector<LoopInfo *> inner(loopInfos); - - for (int i = 0; i < inner.size(); ) { - if (inner.at(i)->nestedLoops.isEmpty()) - ++i; - else - inner.remove(i); - } - - return inner; - } - -private: - void subLoop(BasicBlock *loopHead, const std::vector<BasicBlock *> &backedges) - { - loopHead->markAsGroupStart(); - LoopInfo *info = new LoopInfo; - info->loopHeader = loopHead; - loopInfos.append(info); - - std::vector<BasicBlock *> worklist; - worklist.reserve(backedges.size() + 8); - worklist.insert(worklist.end(), backedges.begin(), backedges.end()); - while (!worklist.empty()) { - BasicBlock *predIt = worklist.back(); - worklist.pop_back(); - - BasicBlock *subloop = predIt->containingGroup(); - if (subloop) { - // This is a discovered block. Find its outermost discovered loop. - while (BasicBlock *parentLoop = subloop->containingGroup()) - subloop = parentLoop; - - // If it is already discovered to be a subloop of this loop, continue. - if (subloop == loopHead) - continue; - - // Yay, it's a subloop of this loop. - subloop->setContainingGroup(loopHead); - predIt = subloop; - - // Add all predecessors of the subloop header to the worklist, as long as - // those predecessors are not in the current subloop. It might be the case - // that they are in other loops, which we will then add as a subloop to the - // current loop. - for (BasicBlock *predIn : predIt->in) - if (predIn->containingGroup() != subloop) - worklist.push_back(predIn); - } else { - if (predIt == loopHead) - continue; - - // This is an undiscovered block. Map it to the current loop. - predIt->setContainingGroup(loopHead); - - // Add all incoming edges to the worklist. - for (BasicBlock *bb : predIt->in) - worklist.push_back(bb); - } - } - } - -private: - const DominatorTree &dt; - QVector<LoopInfo *> loopInfos; - - void createLoopInfos(IR::Function *function) - { - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) - continue; - if (BasicBlock *loopHeader = bb->containingGroup()) - findLoop(loopHeader)->loopBody.append(bb); - } - - for (int i = 0, size = loopInfos.size(); i < size; ++i) { - if (BasicBlock *containingLoopHeader = loopInfos.at(i)->loopHeader->containingGroup()) - findLoop(containingLoopHeader)->addNestedLoop(loopInfos.at(i)); - } - } - - LoopInfo *findLoop(BasicBlock *loopHeader) - { - for (LoopInfo *info : qAsConst(loopInfos)) { - if (info->loopHeader == loopHeader) - return info; - } - - Q_UNREACHABLE(); - return nullptr; - } -}; - -// High-level algorithm: -// 0. start with the first node (the start node) of a function -// 1. emit the node -// 2. add all outgoing edges that are not yet emitted to the postponed stack -// 3. When the postponed stack is empty, pop a stack from the loop stack. If that is empty too, -// we're done. -// 4. pop a node from the postponed stack, and check if it can be scheduled: -// a. if all incoming edges are scheduled, go to 4. -// b. if an incoming edge is unscheduled, but it's a back-edge (an edge in a loop that jumps -// back to the start of the loop), ignore it -// c. if there is any unscheduled edge that is not a back-edge, ignore this node, and go to 4. -// 5. if this node is the start of a loop, push the postponed stack on the loop stack. -// 6. go back to 1. -// -// The postponing action in step 2 will put the node into its containing group. The case where this -// is important is when a (labeled) continue or a (labeled) break statement occur in a loop: the -// outgoing edge points to a node that is not part of the current loop (and possibly not of the -// parent loop). -// -// Linear scan register allocation benefits greatly from short life-time intervals with few holes -// (see for example section 4 (Lifetime Analysis) of [Wimmer1]). This algorithm makes sure that the -// blocks of a group are scheduled together, with no non-loop blocks in between. This applies -// recursively for nested loops. It also schedules groups of if-then-else-endif blocks together for -// the same reason. -class BlockScheduler -{ - enum { DebugBlockScheduler = 0 }; - - IR::Function *function; - const DominatorTree &dominatorTree; - - struct WorkForGroup - { - BasicBlock *group; - QStack<BasicBlock *> postponed; - - WorkForGroup(BasicBlock *group = 0): group(group) {} - }; - WorkForGroup currentGroup; - QStack<WorkForGroup> postponedGroups; - QVector<BasicBlock *> sequence; - ProcessedBlocks emitted; - QHash<BasicBlock *, BasicBlock *> loopsStartEnd; - - bool checkCandidate(BasicBlock *candidate) - { - Q_ASSERT(candidate->containingGroup() == currentGroup.group); - - for (BasicBlock *in : candidate->in) { - if (emitted.alreadyProcessed(in)) - continue; - - if (dominatorTree.dominates(candidate, in)) - // 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 - } - - if (candidate->isGroupStart()) { - // postpone everything, and schedule the loop first. - postponedGroups.push(currentGroup); - currentGroup = WorkForGroup(candidate); - } - - return true; - } - - BasicBlock *pickNext() - { - while (true) { - while (currentGroup.postponed.isEmpty()) { - if (postponedGroups.isEmpty()) - return 0; - if (currentGroup.group) // record the first and the last node of a group - loopsStartEnd.insert(currentGroup.group, sequence.last()); - currentGroup = postponedGroups.pop(); - } - - BasicBlock *next = currentGroup.postponed.pop(); - if (checkCandidate(next)) - return next; - } - - Q_UNREACHABLE(); - return 0; - } - - void emitBlock(BasicBlock *bb) - { - Q_ASSERT(!bb->isRemoved()); - if (emitted.alreadyProcessed(bb)) - return; - - sequence.append(bb); - emitted.markAsProcessed(bb); - } - - void schedule(BasicBlock *functionEntryPoint) - { - BasicBlock *next = functionEntryPoint; - - while (next) { - emitBlock(next); - for (int i = next->out.size(); i != 0; ) { - // postpone all outgoing edges, if they were not already processed - --i; - BasicBlock *out = next->out[i]; - if (!emitted.alreadyProcessed(out)) - postpone(out); - } - next = pickNext(); - } - } - - void postpone(BasicBlock *bb) - { - if (currentGroup.group == bb->containingGroup()) { - currentGroup.postponed.append(bb); - return; - } - - for (int i = postponedGroups.size(); i != 0; ) { - --i; - WorkForGroup &g = postponedGroups[i]; - if (g.group == bb->containingGroup()) { - g.postponed.append(bb); - return; - } - } - - Q_UNREACHABLE(); - } - - void dumpLoopStartsEnds() const - { - qDebug() << "Found" << loopsStartEnd.size() << "loops:"; - for (auto key : loopsStartEnd.keys()) - qDebug("Loop starting at L%d ends at L%d.", key->index(), - loopsStartEnd.value(key)->index()); - } - -public: - BlockScheduler(IR::Function *function, const DominatorTree &dominatorTree) - : function(function) - , dominatorTree(dominatorTree) - , sequence(0) - , emitted(function) - {} - - QHash<BasicBlock *, BasicBlock *> go() - { - showMeTheCode(function, "Before block scheduling"); - if (DebugBlockScheduler) - dominatorTree.dumpImmediateDominators(); - - schedule(function->basicBlock(0)); - - Q_ASSERT(function->liveBasicBlocksCount() == sequence.size()); - function->setScheduledBlocks(sequence); - if (DebugBlockScheduler) - dumpLoopStartsEnds(); - return loopsStartEnd; - } -}; - -#ifndef QT_NO_DEBUG -void checkCriticalEdges(const QVector<BasicBlock *> &basicBlocks) { - for (BasicBlock *bb : basicBlocks) { - if (bb && bb->out.size() > 1) { - for (BasicBlock *bb2 : bb->out) { - if (bb2 && bb2->in.size() > 1) { - qDebug() << "found critical edge between block" - << bb->index() << "and block" << bb2->index(); - Q_ASSERT(false); - } - } - } - } -} -#endif - -static void cleanupBasicBlocks(IR::Function *function) -{ - showMeTheCode(function, "Before basic block cleanup"); - - // Algorithm: this is the iterative version of a depth-first search for all blocks that are - // reachable through outgoing edges, starting with the start block and all exception handler - // blocks. - QBitArray reachableBlocks(function->basicBlockCount()); - QVarLengthArray<BasicBlock *, 16> postponed; - for (int i = 0, ei = function->basicBlockCount(); i != ei; ++i) { - BasicBlock *bb = function->basicBlock(i); - if (i == 0 || bb->isExceptionHandler()) - postponed.append(bb); - } - - while (!postponed.isEmpty()) { - BasicBlock *bb = postponed.back(); - postponed.pop_back(); - if (bb->isRemoved()) // this block was removed before, we don't need to clean it up. - continue; - - reachableBlocks.setBit(bb->index()); - - for (BasicBlock *outBB : bb->out) { - if (!reachableBlocks.at(outBB->index())) - postponed.append(outBB); - } - } - - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) // the block has already been removed, so ignore it - continue; - if (reachableBlocks.at(bb->index())) // the block is reachable, so ignore it - continue; - - for (BasicBlock *outBB : bb->out) { - if (outBB->isRemoved() || !reachableBlocks.at(outBB->index())) - continue; // We do not need to unlink from blocks that are scheduled to be removed. - - int idx = outBB->in.indexOf(bb); - if (idx != -1) { - outBB->in.remove(idx); - for (Stmt *s : outBB->statements()) { - if (Phi *phi = s->asPhi()) - phi->incoming.remove(idx); - else - break; - } - } - } - - function->removeBasicBlock(bb); - } - - showMeTheCode(function, "After basic block cleanup"); -} - -inline Const *isConstPhi(Phi *phi) -{ - if (Const *c = phi->incoming[0]->asConst()) { - for (int i = 1, ei = phi->incoming.size(); i != ei; ++i) { - if (Const *cc = phi->incoming[i]->asConst()) { - if (c->value != cc->value) - return 0; - if (!(c->type == cc->type || (c->type & NumberType && cc->type & NumberType))) - return 0; - if (int(c->value) == 0 && int(cc->value) == 0) - if (isNegative(c->value) != isNegative(cc->value)) - return 0; - } else { - return 0; - } - } - return c; - } - return 0; -} - -static Expr *clone(Expr *e, IR::Function *function) { - if (Temp *t = e->asTemp()) { - return CloneExpr::cloneTemp(t, function); - } else if (Const *c = e->asConst()) { - return CloneExpr::cloneConst(c, function); - } else if (Name *n = e->asName()) { - return CloneExpr::cloneName(n, function); - } else { - Q_UNREACHABLE(); - return e; - } -} - -class ExprReplacer -{ - DefUses &_defUses; - IR::Function* _function; - Temp *_toReplace; - Expr *_replacement; - -public: - ExprReplacer(DefUses &defUses, IR::Function *function) - : _defUses(defUses) - , _function(function) - , _toReplace(0) - , _replacement(0) - {} - - bool operator()(Temp *toReplace, Expr *replacement, StatementWorklist &W, QVector<Stmt *> *newUses = 0) - { - Q_ASSERT(replacement->asTemp() || replacement->asConst() || replacement->asName()); - - qSwap(_toReplace, toReplace); - qSwap(_replacement, replacement); - - const QVector<Stmt *> &uses = _defUses.uses(*_toReplace); - - // Prevent the following: - // L3: - // %1 = phi L1: %2, L2: %3 - // %4 = phi L1: %5, L2: %6 - // %6 = %1 - // From turning into: - // L3: - // %1 = phi L1: %2, L2: %3 - // %4 = phi L1: %5, L2: %1 - // - // Because both phi nodes are "executed in parallel", we cannot replace %6 by %1 in the - // second phi node. So, if the defining statement for a temp is a phi node, and one of the - // uses of the to-be-replaced statement is a phi node in the same block as the defining - // statement, bail out. - if (Temp *r = _replacement->asTemp()) { - if (_defUses.defStmt(*r)->asPhi()) { - BasicBlock *replacementDefBlock = _defUses.defStmtBlock(*r); - for (Stmt *use : uses) { - if (Phi *usePhi = use->asPhi()) { - if (_defUses.defStmtBlock(*usePhi->targetTemp) == replacementDefBlock) - return false; - } - } - } - } - -// qout << "Replacing ";toReplace->dump(qout);qout<<" by ";replacement->dump(qout);qout<<endl; - - if (newUses) - newUses->reserve(uses.size()); - -// qout << " " << uses.size() << " uses:"<<endl; - for (Stmt *use : uses) { -// qout<<" ";use->dump(qout);qout<<"\n"; - visit(use); -// qout<<" -> ";use->dump(qout);qout<<"\n"; - W += use; - if (newUses) - newUses->push_back(use); - } - - qSwap(_replacement, replacement); - qSwap(_toReplace, toReplace); - return true; - } - -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); - } - void visitNew(New *e) { - check(e->base); - for (ExprList *it = e->args; it; it = it->next) - check(it->expr); - } - 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)) { - e = clone(_replacement, _function); - } else { - visit(e); - } - } - - // This only calculates equality for everything needed by constant propagation - bool equals(Expr *e1, Expr *e2) const { - if (e1 == e2) - return true; - - if (Const *c1 = e1->asConst()) { - if (Const *c2 = e2->asConst()) - return c1->value == c2->value && (c1->type == c2->type || (c1->type & NumberType && c2->type & NumberType)); - } else if (Temp *t1 = e1->asTemp()) { - if (Temp *t2 = e2->asTemp()) - return *t1 == *t2; - } else if (Name *n1 = e1->asName()) { - if (Name *n2 = e2->asName()) { - if (n1->id) { - if (n2->id) - return *n1->id == *n2->id; - } else { - return n1->builtin == n2->builtin; - } - } - } - - if (e1->type == IR::NullType && e2->type == IR::NullType) - return true; - if (e1->type == IR::UndefinedType && e2->type == IR::UndefinedType) - return true; - - return false; - } -}; - -namespace { -/// This function removes the basic-block from the function's list, unlinks any uses and/or defs, -/// and removes unreachable staements from the worklist, so that optimiseSSA won't consider them -/// anymore. -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) - { - int idx = to->in.indexOf(from); - if (idx == -1) - return; - - to->in.remove(idx); - for (Stmt *outStmt : to->statements()) { - if (!outStmt) - continue; - if (Phi *phi = outStmt->asPhi()) { - if (Temp *t = phi->incoming[idx]->asTemp()) { - defUses.removeUse(phi, *t); - W += defUses.defStmt(*t); - } - phi->incoming.remove(idx); - W += phi; - } else { - break; - } - } - } - - static bool isReachable(BasicBlock *bb, const DominatorTree &dt) - { - for (BasicBlock *in : bb->in) { - if (in->isRemoved()) - continue; - if (dt.dominates(bb, in)) // a back-edge, not interesting - continue; - return true; - } - - return false; - } - }; - - 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); - - BasicBlockSet siblings; - siblings.init(func); - - // 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); - toPurge.append(to); - while (!toPurge.isEmpty()) { - BasicBlock *bb = toPurge.first(); - toPurge.removeFirst(); - if (DebugUnlinking) - qDebug("... purging L%d", bb->index()); - - if (bb->isRemoved()) - continue; - - // unlink all incoming edges - for (BasicBlock *in : bb->in) { - int idx = in->out.indexOf(bb); - if (idx != -1) - in->out.remove(idx); - } - - // unlink all outgoing edges, including "arguments" to phi statements - for (BasicBlock *out : bb->out) { - if (out->isRemoved()) - continue; - - Util::removeIncomingEdge(bb, out, defUses, W); - - if (Util::isReachable(out, dt)) { - dt.collectSiblings(out, siblings); - } else { - // if a successor has no incoming edges after unlinking the current basic block, - // then it is unreachable, and can be purged too - toPurge.append(out); - } - } - - // unlink all defs/uses from the statements in the basic block - for (Stmt *s : bb->statements()) { - if (!s) - continue; - - W += defUses.removeDefUses(s); - W -= s; - } - - siblings.remove(bb); - dt.setImmediateDominator(bb, 0); - func->removeBasicBlock(bb); - } - } - - dt.recalculateIDoms(siblings); - if (DebugUnlinking) - qDebug("Unlinking done."); -} - -bool tryOptimizingComparison(Expr *&expr) -{ - Binop *b = expr->asBinop(); - if (!b) - return false; - Const *leftConst = b->left->asConst(); - if (!leftConst || leftConst->type == StringType || leftConst->type == VarType || leftConst->type == QObjectType) - return false; - Const *rightConst = b->right->asConst(); - if (!rightConst || rightConst->type == StringType || rightConst->type == VarType || rightConst->type == QObjectType) - return false; - - QV4::Primitive l = convertToValue(leftConst); - QV4::Primitive r = convertToValue(rightConst); - - switch (b->op) { - case OpGt: - leftConst->value = Runtime::method_compareGreaterThan(l, r); - leftConst->type = BoolType; - expr = leftConst; - return true; - case OpLt: - leftConst->value = Runtime::method_compareLessThan(l, r); - leftConst->type = BoolType; - expr = leftConst; - return true; - case OpGe: - leftConst->value = Runtime::method_compareGreaterEqual(l, r); - leftConst->type = BoolType; - expr = leftConst; - return true; - case OpLe: - leftConst->value = Runtime::method_compareLessEqual(l, r); - leftConst->type = BoolType; - expr = leftConst; - return true; - case OpStrictEqual: - leftConst->value = Runtime::method_compareStrictEqual(l, r); - leftConst->type = BoolType; - expr = leftConst; - return true; - case OpEqual: - leftConst->value = Runtime::method_compareEqual(l, r); - leftConst->type = BoolType; - expr = leftConst; - return true; - case OpStrictNotEqual: - leftConst->value = Runtime::method_compareStrictNotEqual(l, r); - leftConst->type = BoolType; - expr = leftConst; - return true; - case OpNotEqual: - leftConst->value = Runtime::method_compareNotEqual(l, r); - leftConst->type = BoolType; - expr = leftConst; - return true; - default: - break; - } - - return false; -} - -void cfg2dot(IR::Function *f, const QVector<LoopDetection::LoopInfo *> &loops = QVector<LoopDetection::LoopInfo *>()) -{ - static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_IR"); - if (!showCode) - return; - - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - - struct Util { - QTextStream &qout; - Util(QTextStream &qout): qout(qout) {} - void genLoop(const LoopDetection::LoopInfo *loop) - { - qout << " subgraph \"cluster" << quint64(loop) << "\" {\n"; - qout << " L" << loop->loopHeader->index() << ";\n"; - for (BasicBlock *bb : loop->loopBody) - qout << " L" << bb->index() << ";\n"; - for (LoopDetection::LoopInfo *nested : loop->nestedLoops) - genLoop(nested); - qout << " }\n"; - } - }; - - QString name; - if (f->name) name = *f->name; - else name = QStringLiteral("%1").arg((unsigned long long)f); - qout << "digraph \"" << name << "\" { ordering=out;\n"; - - for (LoopDetection::LoopInfo *l : loops) { - if (l->parentLoop == 0) - Util(qout).genLoop(l); - } - - for (BasicBlock *bb : f->basicBlocks()) { - if (bb->isRemoved()) - continue; - - int idx = bb->index(); - qout << " L" << idx << " [label=\"L" << idx << "\""; - if (idx == 0 || bb->terminator()->asRet()) - qout << ", shape=doublecircle"; - else - qout << ", shape=circle"; - qout << "];\n"; - for (BasicBlock *out : bb->out) - qout << " L" << idx << " -> L" << out->index() << "\n"; - } - - qout << "}\n"; - buf.close(); - qDebug("%s", buf.data().constData()); -} - -} // anonymous namespace - -void optimizeSSA(StatementWorklist &W, DefUses &defUses, DominatorTree &df) -{ - IR::Function *function = W.function(); - ExprReplacer replaceUses(defUses, function); - - Stmt *s = 0; - while ((s = W.takeNext(s))) { - - if (Phi *phi = s->asPhi()) { - // dead code elimination: - if (defUses.useCount(*phi->targetTemp) == 0) { - W += defUses.removeDefUses(phi); - W.remove(s); - continue; - } - - // constant propagation: - if (Const *c = isConstPhi(phi)) { - replaceUses(phi->targetTemp, c, W); - defUses.removeDef(*phi->targetTemp); - W.remove(s); - continue; - } - - // copy propagation: - if (phi->incoming.size() == 1) { - Temp *t = phi->targetTemp; - Expr *e = phi->incoming.first(); - - QVector<Stmt *> newT2Uses; - replaceUses(t, e, W, &newT2Uses); - if (Temp *t2 = e->asTemp()) { - defUses.removeUse(s, *t2); - defUses.addUses(*t2, newT2Uses); - W += defUses.defStmt(*t2); - } - defUses.removeDef(*t); - W.remove(s); - continue; - } - } else if (Move *m = s->asMove()) { - if (Convert *convert = m->source->asConvert()) { - if (Const *sourceConst = convert->expr->asConst()) { - convertConst(sourceConst, convert->type); - m->source = sourceConst; - W += m; - continue; - } else if (Temp *sourceTemp = convert->expr->asTemp()) { - if (sourceTemp->type == convert->type) { - m->source = sourceTemp; - W += m; - continue; - } - } - } - - if (Temp *targetTemp = m->target->asTemp()) { - // dead code elimination: - if (defUses.useCount(*targetTemp) == 0) { - EliminateDeadCode(defUses, W).run(m->source, s); - if (!m->source) - W.remove(s); - continue; - } - - // constant propagation: - if (Const *sourceConst = m->source->asConst()) { - Q_ASSERT(sourceConst->type != UnknownType); - replaceUses(targetTemp, sourceConst, W); - defUses.removeDef(*targetTemp); - W.remove(s); - continue; - } - if (Member *member = m->source->asMember()) { - if (member->kind == Member::MemberOfEnum) { - Const *c = function->New<Const>(); - const int enumValue = member->enumValue; - c->init(SInt32Type, enumValue); - replaceUses(targetTemp, c, W); - defUses.removeDef(*targetTemp); - W.remove(s); - defUses.removeUse(s, *member->base->asTemp()); - continue; - } else if (member->kind != IR::Member::MemberOfIdObjectsArray && member->attachedPropertiesId != 0 && member->property && member->base->asTemp()) { - // Attached properties have no dependency on their base. Isel doesn't - // need it and we can eliminate the temp used to initialize it. - defUses.removeUse(s, *member->base->asTemp()); - Const *c = function->New<Const>(); - c->init(SInt32Type, 0); - member->base = c; - continue; - } - } - - // copy propagation: - if (Temp *sourceTemp = m->source->asTemp()) { - QVector<Stmt *> newT2Uses; - if (replaceUses(targetTemp, sourceTemp, W, &newT2Uses)) { - defUses.removeUse(s, *sourceTemp); - defUses.addUses(*sourceTemp, newT2Uses); - defUses.removeDef(*targetTemp); - W.remove(s); - } - continue; - } - - if (Unop *unop = m->source->asUnop()) { - // Constant unary expression evaluation: - if (Const *constOperand = unop->expr->asConst()) { - if (constOperand->type & NumberType || constOperand->type == BoolType) { - // TODO: implement unop propagation for other constant types - bool doneSomething = false; - switch (unop->op) { - case OpNot: - constOperand->value = !constOperand->value; - constOperand->type = BoolType; - doneSomething = true; - break; - case OpUMinus: - if (int(constOperand->value) == 0 && int(constOperand->value) == constOperand->value) { - if (isNegative(constOperand->value)) - constOperand->value = 0; - else - constOperand->value = -1 / Q_INFINITY; - constOperand->type = DoubleType; - doneSomething = true; - break; - } - - constOperand->value = -constOperand->value; - doneSomething = true; - break; - case OpUPlus: - if (unop->type != UnknownType) - constOperand->type = unop->type; - doneSomething = true; - break; - case OpCompl: - constOperand->value = ~QV4::Primitive::toInt32(constOperand->value); - constOperand->type = SInt32Type; - doneSomething = true; - break; - case OpIncrement: - constOperand->value = constOperand->value + 1; - doneSomething = true; - break; - case OpDecrement: - constOperand->value = constOperand->value - 1; - doneSomething = true; - break; - default: - break; - }; - - if (doneSomething) { - m->source = constOperand; - W += m; - } - } - } - // TODO: if the result of a unary not operation is only used in a cjump, - // then inline it. - - continue; - } - - if (Binop *binop = m->source->asBinop()) { - Const *leftConst = binop->left->asConst(); - Const *rightConst = binop->right->asConst(); - - { // Typical casts to int32: - Expr *casted = 0; - switch (binop->op) { - case OpBitAnd: - if (leftConst && !rightConst && QV4::Primitive::toUInt32(leftConst->value) == 0xffffffff) - casted = binop->right; - else if (!leftConst && rightConst && QV4::Primitive::toUInt32(rightConst->value) == 0xffffffff) - casted = binop->left; - break; - case OpBitOr: - if (leftConst && !rightConst && QV4::Primitive::toInt32(leftConst->value) == 0) - casted = binop->right; - else if (!leftConst && rightConst && QV4::Primitive::toUInt32(rightConst->value) == 0) - casted = binop->left; - break; - default: - break; - } - if (casted && casted->type == SInt32Type) { - m->source = casted; - W += m; - continue; - } - } - if (rightConst) { - switch (binop->op) { - case OpLShift: - case OpRShift: - if (double v = QV4::Primitive::toInt32(rightConst->value) & 0x1f) { - // mask right hand side of shift operations - rightConst->value = v; - rightConst->type = SInt32Type; - } else { - // shifting a value over 0 bits is a move: - if (rightConst->value == 0) { - m->source = binop->left; - W += m; - } - } - - break; - default: - break; - } - } - - // TODO: More constant binary expression evaluation - // TODO: If the result of the move is only used in one single cjump, then - // inline the binop into the cjump. - if (!leftConst || leftConst->type == StringType || leftConst->type == VarType || leftConst->type == QObjectType) - continue; - if (!rightConst || rightConst->type == StringType || rightConst->type == VarType || rightConst->type == QObjectType) - continue; - - QV4::Primitive lc = convertToValue(leftConst); - QV4::Primitive rc = convertToValue(rightConst); - double l = lc.toNumber(); - double r = rc.toNumber(); - - switch (binop->op) { - case OpMul: - leftConst->value = l * r; - leftConst->type = DoubleType; - m->source = leftConst; - W += m; - break; - case OpAdd: - leftConst->value = l + r; - leftConst->type = DoubleType; - m->source = leftConst; - W += m; - break; - case OpSub: - leftConst->value = l - r; - leftConst->type = DoubleType; - m->source = leftConst; - W += m; - break; - case OpDiv: - leftConst->value = l / r; - leftConst->type = DoubleType; - m->source = leftConst; - W += m; - break; - case OpMod: - leftConst->value = std::fmod(l, r); - leftConst->type = DoubleType; - m->source = leftConst; - W += m; - break; - default: - if (tryOptimizingComparison(m->source)) - W += m; - break; - } - - continue; - } - } // TODO: var{#0} = double{%10} where %10 is defined once and used once. E.g.: function(t){t = t % 2; return t; } - - } else if (CJump *cjump = s->asCJump()) { - if (Const *constantCondition = cjump->cond->asConst()) { - // Note: this assumes that there are no critical edges! Meaning, we can safely purge - // any basic blocks that are found to be unreachable. - Jump *jump = function->NewStmt<Jump>(); - W.registerNewStatement(jump); - if (convertToValue(constantCondition).toBoolean()) { - jump->target = cjump->iftrue; - unlink(cjump->parent, cjump->iffalse, function, defUses, W, df); - } else { - jump->target = cjump->iffalse; - unlink(cjump->parent, cjump->iftrue, function, defUses, W, df); - } - W.replace(s, jump); - - continue; - } else if (cjump->cond->asBinop()) { - if (tryOptimizingComparison(cjump->cond)) - W += cjump; - continue; - } - // TODO: Constant unary expression evaluation - // TODO: if the expression is an unary not operation, lift the expression, and switch - // the then/else blocks. - } - } - - W.applyToFunction(); -} - -//### TODO: use DefUses from the optimizer, because it already has all this information -class InputOutputCollector -{ - void setOutput(Temp *out) - { - Q_ASSERT(!output); - output = out; - } - -public: - std::vector<Temp *> inputs; - Temp *output; - - InputOutputCollector() - { inputs.reserve(4); } - - void collect(Stmt *s) { - inputs.resize(0); - output = 0; - visit(s); - } - -private: - void visit(Expr *e) - { - if (auto t = e->asTemp()) { - inputs.push_back(t); - } else { - EXPR_VISIT_ALL_KINDS(e); - } - } - - 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); - } - } -}; - -/* - * The algorithm is described in: - * - * Linear Scan Register Allocation on SSA Form - * Christian Wimmer & Michael Franz, CGO'10, April 24-28, 2010 - * - * See LifeTimeIntervals::renumber for details on the numbering. - */ -class LifeRanges { - 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; - LifeTimeIntervals::Ptr _sortedIntervals; - - LifeTimeInterval &interval(const Temp *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; - } - - int usePosition(IR::Stmt *s) const - { - return _sortedIntervals->positionForStatement(s); - } - - int start(IR::BasicBlock *bb) const - { - return _sortedIntervals->startPosition(bb); - } - - int end(IR::BasicBlock *bb) const - { - return _sortedIntervals->endPosition(bb); - } - -public: - LifeRanges(IR::Function *function, const QHash<BasicBlock *, BasicBlock *> &startEndLoops) - : _intervals(function->tempCount) - { - _sortedIntervals = LifeTimeIntervals::create(function); - _liveIn.resize(function->basicBlockCount()); - - for (int i = function->basicBlockCount() - 1; i >= 0; --i) { - BasicBlock *bb = function->basicBlock(i); - buildIntervals(bb, startEndLoops.value(bb, 0)); - } - - _intervals.clear(); - } - - LifeTimeIntervals::Ptr intervals() const { return _sortedIntervals; } - - void dump() const - { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream qout(&buf); - - qout << "Life ranges:" << endl; - qout << "Intervals:" << endl; - const auto intervals = _sortedIntervals->intervals(); - for (const LifeTimeInterval *range : intervals) { - range->dump(qout); - qout << endl; - } - - IRPrinter printer(&qout); - for (size_t i = 0, ei = _liveIn.size(); i != ei; ++i) { - qout << "L" << i <<" live-in: "; - 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 << ", "; - qout << '%' << live.at(i); - } - qout << endl; - } - buf.close(); - qDebug("%s", buf.data().constData()); - } - -private: - void buildIntervals(BasicBlock *bb, BasicBlock *loopEnd) - { - LiveRegs live; - for (BasicBlock *successor : bb->out) { - live.unite(_liveIn[successor->index()]); - const int bbIndex = successor->in.indexOf(bb); - Q_ASSERT(bbIndex >= 0); - - for (Stmt *s : successor->statements()) { - if (Phi *phi = s->asPhi()) { - if (Temp *t = phi->incoming.at(bbIndex)->asTemp()) { - ensureInterval(*t); - live.insert(t->index); - } - } else { - break; - } - } - } - - const QVector<Stmt *> &statements = bb->statements(); - - 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()) { - 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)); - } else { - live.erase(it); - } - _sortedIntervals->add(&interval(phi->targetTemp)); - continue; - } - 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().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->index); - } - } - - if (loopEnd) { // Meaning: bb is a loop header, because loopEnd is set to non-null. - for (int reg : live) - _intervals[reg]->addRange(start(bb), usePosition(loopEnd->terminator())); - } - - _liveIn[bb->index()] = std::move(live); - } -}; - -void removeUnreachleBlocks(IR::Function *function) -{ - QVector<BasicBlock *> newSchedule; - newSchedule.reserve(function->basicBlockCount()); - for (BasicBlock *bb : function->basicBlocks()) - if (!bb->isRemoved()) - newSchedule.append(bb); - function->setScheduledBlocks(newSchedule); -} - -class ConvertArgLocals -{ -public: - ConvertArgLocals(IR::Function *function) - : function(function) - , convertArgs(!function->usesArgumentsObject) - { - tempForFormal.resize(function->formals.size(), -1); - tempForLocal.resize(function->locals.size(), -1); - } - - void toTemps() - { - if (function->variablesCanEscape()) - return; - - QVector<Stmt *> extraMoves; - if (convertArgs) { - const int formalCount = function->formals.size(); - extraMoves.reserve(formalCount + function->basicBlock(0)->statementCount()); - extraMoves.resize(formalCount); - - for (int i = 0; i != formalCount; ++i) { - const int newTemp = function->tempCount++; - tempForFormal[i] = newTemp; - - ArgLocal *source = function->New<ArgLocal>(); - source->init(ArgLocal::Formal, i, 0); - - Temp *target = function->New<Temp>(); - target->init(Temp::VirtualRegister, newTemp); - - Move *m = function->NewStmt<Move>(); - m->init(target, source); - extraMoves[i] = m; - } - } - - 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); - - function->locals.clear(); - } - -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); - } - } - - 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); - } - } - - void check(Expr *&e) { - if (ArgLocal *al = e->asArgLocal()) { - if (al->kind == ArgLocal::Local) { - Temp *t = function->New<Temp>(); - t->init(Temp::VirtualRegister, fetchTempForLocal(al->index)); - e = t; - } else if (convertArgs && al->kind == ArgLocal::Formal) { - Temp *t = function->New<Temp>(); - t->init(Temp::VirtualRegister, fetchTempForFormal(al->index)); - e = t; - } - } else { - visit(e); - } - } - - int fetchTempForLocal(int local) - { - int &ref = tempForLocal[local]; - if (ref == -1) - ref = function->tempCount++; - return ref; - } - - int fetchTempForFormal(int formal) - { - return tempForFormal[formal]; - } - - IR::Function *function; - bool convertArgs; - std::vector<int> tempForFormal; - std::vector<int> tempForLocal; -}; - -class CloneBasicBlock: protected CloneExpr -{ -public: - BasicBlock *operator()(IR::BasicBlock *originalBlock) - { - block = new BasicBlock(originalBlock->function, 0); - - for (Stmt *s : originalBlock->statements()) { - visit(s); - clonedStmt->location = s->location; - } - - return block; - } - -private: - void visit(Stmt *s) - { - 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); - for (Expr *in : p->incoming) - phi->incoming.append(clone(in)); - block->appendStatement(phi); - } else { - Q_UNREACHABLE(); - } - } - -private: - IR::Stmt *clonedStmt; -}; - -static void verifyCFG(IR::Function *function) -{ - if (!DoVerification) - return; - - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) { - Q_ASSERT(bb->in.isEmpty()); - Q_ASSERT(bb->out.isEmpty()); - continue; - } - - Q_ASSERT(function->basicBlock(bb->index()) == bb); - - // Check the terminators: - 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 = terminator->asCJump()) { - Q_UNUSED(cjump); - Q_ASSERT(bb->out.size() == 2); - Q_ASSERT(cjump->iftrue); - Q_ASSERT(!cjump->iftrue->isRemoved()); - Q_ASSERT(cjump->iftrue == bb->out[0]); - Q_ASSERT(cjump->iffalse); - Q_ASSERT(!cjump->iffalse->isRemoved()); - Q_ASSERT(cjump->iffalse == bb->out[1]); - } else if (terminator->asRet()) { - Q_ASSERT(bb->out.size() == 0); - } else { - Q_UNREACHABLE(); - } - - // Check the outgoing edges: - for (BasicBlock *out : bb->out) { - Q_UNUSED(out); - Q_ASSERT(!out->isRemoved()); - Q_ASSERT(out->in.contains(bb)); - } - - // Check the incoming edges: - for (BasicBlock *in : bb->in) { - Q_UNUSED(in); - Q_ASSERT(!in->isRemoved()); - Q_ASSERT(in->out.contains(bb)); - } - } -} - -static void verifyImmediateDominators(const DominatorTree &dt, IR::Function *function) -{ - if (!DoVerification) - return; - - cfg2dot(function); - dt.dumpImmediateDominators(); - DominatorTree referenceTree(function); - - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) - continue; - - BasicBlock *idom = dt.immediateDominator(bb); - BasicBlock *referenceIdom = referenceTree.immediateDominator(bb); - Q_UNUSED(idom); - Q_UNUSED(referenceIdom); - Q_ASSERT(idom == referenceIdom); - } -} - -static void verifyNoPointerSharing(IR::Function *function) -{ - if (!DoVerification) - return; - - class { - public: - void operator()(IR::Function *f) - { - for (BasicBlock *bb : f->basicBlocks()) { - if (bb->isRemoved()) - continue; - - for (Stmt *s : bb->statements()) { - visit(s); - } - } - } - - private: - void visit(Stmt *s) - { - check(s); - STMT_VISIT_ALL_KINDS(s); - } - - void visit(Expr *e) - { - check(e); - EXPR_VISIT_ALL_KINDS(e); - } - - private: - void check(Stmt *s) - { - Q_ASSERT(!stmts.contains(s)); - stmts.insert(s); - } - - void check(Expr *e) - { - Q_ASSERT(!exprs.contains(e)); - exprs.insert(e); - } - - QSet<Stmt *> stmts; - QSet<Expr *> exprs; - } V; - V(function); -} - -// Loop-peeling is done by unfolding the loop once. The "original" loop basic blocks stay where they -// are, and a copy of the loop is placed after it. Special care is taken while copying the loop body: -// by having the copies of the basic-blocks point to the same nodes as the "original" basic blocks, -// updating the immediate dominators is easy: if the edge of a copied basic-block B points to a -// block C that has also been copied, set the immediate dominator of B to the corresponding -// immediate dominator of C. Finally, for any node outside the loop that gets a new edge attached, -// the immediate dominator has to be re-calculated. -class LoopPeeling -{ - DominatorTree &dt; - -public: - LoopPeeling(DominatorTree &dt) - : dt(dt) - {} - - void run(const QVector<LoopDetection::LoopInfo *> &loops) - { - for (LoopDetection::LoopInfo *loopInfo : loops) - peelLoop(loopInfo); - } - -private: - // All copies have their outgoing edges pointing to the same successor block as the originals. - // For each copied block, check where the outgoing edges point to. If it's a block inside the - // (original) loop, rewire it to the corresponding copy. Otherwise, which is when it points - // out of the loop, leave it alone. - // As an extra, collect all edges that point out of the copied loop, because the targets need - // to have their immediate dominator rechecked. - void rewire(BasicBlock *newLoopBlock, const QVector<BasicBlock *> &from, const QVector<BasicBlock *> &to, QVector<BasicBlock *> &loopExits) - { - for (int i = 0, ei = newLoopBlock->out.size(); i != ei; ++i) { - BasicBlock *&out = newLoopBlock->out[i]; - const int idx = from.indexOf(out); - if (idx == -1) { - if (!loopExits.contains(out)) - loopExits.append(out); - } else { - out->in.removeOne(newLoopBlock); - BasicBlock *newTo = to.at(idx); - newTo->in.append(newLoopBlock); - out = newTo; - - Stmt *terminator = newLoopBlock->terminator(); - if (Jump *jump = terminator->asJump()) { - Q_ASSERT(i == 0); - jump->target = newTo; - } else if (CJump *cjump = terminator->asCJump()) { - Q_ASSERT(i == 0 || i == 1); - if (i == 0) - cjump->iftrue = newTo; - else - cjump->iffalse = newTo; - } - } - } - } - - void peelLoop(LoopDetection::LoopInfo *loop) - { - IR::Function *f = loop->loopHeader->function; - CloneBasicBlock clone; - - LoopDetection::LoopInfo unpeeled(*loop); - unpeeled.loopHeader = clone(unpeeled.loopHeader); - unpeeled.loopHeader->setContainingGroup(loop->loopHeader->containingGroup()); - unpeeled.loopHeader->markAsGroupStart(true); - f->addBasicBlock(unpeeled.loopHeader); - for (int i = 0, ei = unpeeled.loopBody.size(); i != ei; ++i) { - BasicBlock *&bodyBlock = unpeeled.loopBody[i]; - bodyBlock = clone(bodyBlock); - bodyBlock->setContainingGroup(unpeeled.loopHeader); - Q_ASSERT(bodyBlock->statementCount() == loop->loopBody[i]->statementCount()); - } - - // The cloned blocks will have no incoming edges, but they do have outgoing ones (copying - // the terminators will automatically insert that edge). The blocks where the originals - // pointed to will have an extra incoming edge from the copied blocks. - - BasicBlock::IncomingEdges inCopy = loop->loopHeader->in; - for (BasicBlock *in : inCopy) { - if (loop->loopHeader != in // this can happen for really tight loops (where there are no body blocks). This is a back-edge in that case. - && unpeeled.loopHeader != in && !unpeeled.loopBody.contains(in) // if the edge is not coming from within the copied set, leave it alone - && !dt.dominates(loop->loopHeader, in)) // an edge coming from within the loop (so a back-edge): this is handled when rewiring all outgoing edges - continue; - - unpeeled.loopHeader->in.append(in); - loop->loopHeader->in.removeOne(in); - - Stmt *terminator = in->terminator(); - if (Jump *jump = terminator->asJump()) { - jump->target = unpeeled.loopHeader; - in->out[0] = unpeeled.loopHeader; - } else if (CJump *cjump = terminator->asCJump()) { - if (cjump->iftrue == loop->loopHeader) { - cjump->iftrue = unpeeled.loopHeader; - Q_ASSERT(in->out[0] == loop->loopHeader); - in->out[0] = unpeeled.loopHeader; - } else if (cjump->iffalse == loop->loopHeader) { - cjump->iffalse = unpeeled.loopHeader; - Q_ASSERT(in->out[1] == loop->loopHeader); - in->out[1] = unpeeled.loopHeader; - } else { - Q_UNREACHABLE(); - } - } - } - - QVector<BasicBlock *> loopExits; - loopExits.reserve(8); - loopExits.append(unpeeled.loopHeader); - - rewire(unpeeled.loopHeader, loop->loopBody, unpeeled.loopBody, loopExits); - for (int i = 0, ei = unpeeled.loopBody.size(); i != ei; ++i) { - BasicBlock *bodyBlock = unpeeled.loopBody.at(i); - rewire(bodyBlock, loop->loopBody, unpeeled.loopBody, loopExits); - f->addBasicBlock(bodyBlock); - } - - // The original loop is now peeled off, and won't jump back to the loop header. Meaning, it - // is not a loop anymore, so unmark it. - loop->loopHeader->markAsGroupStart(false); - for (BasicBlock *bb : qAsConst(loop->loopBody)) - bb->setContainingGroup(loop->loopHeader->containingGroup()); - - // Set the immediate dominator of the new loop header to the old one. The real immediate - // dominator will be calculated later. - dt.setImmediateDominator(unpeeled.loopHeader, loop->loopHeader); - // calculate the idoms in a separate loop, because addBasicBlock in the previous loop will - // set the block index, which in turn is used by the dominator tree. - for (int i = 0, ei = unpeeled.loopBody.size(); i != ei; ++i) { - BasicBlock *bodyBlock = unpeeled.loopBody.at(i); - BasicBlock *idom = dt.immediateDominator(loop->loopBody.at(i)); - const int idx = loop->loopBody.indexOf(idom); - if (idom == loop->loopHeader) - idom = unpeeled.loopHeader; - else if (idx != -1) - idom = unpeeled.loopBody.at(idx); - Q_ASSERT(idom); - dt.setImmediateDominator(bodyBlock, idom); - } - - BasicBlockSet siblings(f); - for (BasicBlock *bb : qAsConst(loopExits)) - dt.collectSiblings(bb, siblings); - - siblings.insert(unpeeled.loopHeader); - dt.recalculateIDoms(siblings, loop->loopHeader); - dt.dumpImmediateDominators(); - verifyImmediateDominators(dt, f); - } -}; - -class RemoveLineNumbers: private SideEffectsChecker -{ -public: - static void run(IR::Function *function) - { - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) - continue; - - for (Stmt *s : bb->statements()) { - if (!hasSideEffects(s)) { - s->location = QQmlJS::AST::SourceLocation(); - } - } - } - } - -private: - ~RemoveLineNumbers() {} - - static bool hasSideEffects(Stmt *stmt) - { - RemoveLineNumbers 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 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.prepend(LifeTimeIntervalRange(from, from)); - if (_end == InvalidPosition) - _end = from; - } else { - _ranges.first().start = from; - } -} - -void LifeTimeInterval::addRange(int from, int to) { - Q_ASSERT(from > 0); - Q_ASSERT(to > 0); - Q_ASSERT(to >= from); - - if (_ranges.isEmpty()) { - _ranges.prepend(LifeTimeIntervalRange(from, to)); - _end = to; - return; - } - - LifeTimeIntervalRange *p = &_ranges.first(); - if (to + 1 >= p->start && p->end + 1 >= from) { - p->start = qMin(p->start, from); - p->end = qMax(p->end, to); - while (_ranges.count() > 1) { - LifeTimeIntervalRange *p1 = p + 1; - if (p->end + 1 < p1->start || p1->end + 1 < p->start) - break; - p1->start = qMin(p->start, p1->start); - p1->end = qMax(p->end, p1->end); - _ranges.remove(0); - p = &_ranges.first(); - } - } else { - if (to < p->start) { - _ranges.prepend(LifeTimeIntervalRange(from, to)); - } else { - Q_ASSERT(from > _ranges.last().end); - _ranges.push_back(LifeTimeIntervalRange(from, to)); - } - } - - _end = _ranges.last().end; -} - -LifeTimeInterval LifeTimeInterval::split(int atPosition, int newStart) -{ - Q_ASSERT(atPosition < newStart || newStart == InvalidPosition); - Q_ASSERT(atPosition <= _end); - Q_ASSERT(newStart <= _end || newStart == InvalidPosition); - - if (_ranges.isEmpty() || atPosition < _ranges.first().start) - return LifeTimeInterval(); - - LifeTimeInterval newInterval = *this; - newInterval.setSplitFromInterval(true); - - // search where to split the interval - for (int i = 0, ei = _ranges.size(); i < ei; ++i) { - if (_ranges.at(i).start <= atPosition) { - if (_ranges.at(i).end >= atPosition) { - // split happens in the middle of a range. Keep this range in both the old and the - // new interval, and correct the end/start later - _ranges.resize(i + 1); - newInterval._ranges.remove(0, i); - break; - } - } else { - // split happens between two ranges. - _ranges.resize(i); - newInterval._ranges.remove(0, i); - break; - } - } - - if (newInterval._ranges.first().end == atPosition) - newInterval._ranges.remove(0); - - if (newStart == InvalidPosition) { - // the temp stays inactive for the rest of its lifetime - newInterval = LifeTimeInterval(); - } else { - // find the first range where the temp will get active again: - while (!newInterval._ranges.isEmpty()) { - const LifeTimeIntervalRange &range = newInterval._ranges.first(); - if (range.start > newStart) { - // The split position is before the start of the range. Either we managed to skip - // over the correct range, or we got an invalid split request. Either way, this - // Should Never Happen <TM>. - Q_ASSERT(range.start > newStart); - return LifeTimeInterval(); - } else if (range.start <= newStart && range.end >= newStart) { - // yay, we found the range that should be the new first range in the new interval! - break; - } else { - // the temp stays inactive for this interval, so remove it. - newInterval._ranges.remove(0); - } - } - Q_ASSERT(!newInterval._ranges.isEmpty()); - newInterval._ranges.first().start = newStart; - _end = newStart; - } - - // if we're in the middle of a range, set the end to the split position - if (_ranges.last().end > atPosition) - _ranges.last().end = atPosition; - - validate(); - newInterval.validate(); - - return newInterval; -} - -void LifeTimeInterval::dump(QTextStream &out) const { - IRPrinter(&out).print(const_cast<Temp *>(&_temp)); - out << ": ends at " << _end << " with ranges "; - if (_ranges.isEmpty()) - out << "(none)"; - for (int i = 0; i < _ranges.size(); ++i) { - if (i > 0) out << ", "; - out << _ranges[i].start << " - " << _ranges[i].end; - } - if (_reg != InvalidRegister) - out << " (register " << _reg << ")"; -} - - -bool LifeTimeInterval::lessThanForTemp(const LifeTimeInterval *r1, const LifeTimeInterval *r2) -{ - return r1->temp() < r2->temp(); -} - -LifeTimeIntervals::LifeTimeIntervals(IR::Function *function) - : _basicBlockPosition(function->basicBlockCount()) - , _positionForStatement(function->statementCount(), IR::Stmt::InvalidId) - , _lastPosition(0) -{ - _intervals.reserve(function->tempCount + 32); // we reserve a bit more space for intervals, because the register allocator will add intervals with fixed ranges for each register. - renumber(function); -} - -// Renumbering works as follows: -// - phi statements are not numbered -// - statement numbers start at 0 (zero) and increment get an even number (lastPosition + 2) -// - basic blocks start at firstStatementNumber - 1, or rephrased: lastPosition + 1 -// - basic blocks end at the number of the last statement -// And during life-time calculation the next rule is used: -// - any temporary starts its life-time at definingStatementPosition + 1 -// -// This numbering simulates half-open intervals. For example: -// 0: %1 = 1 -// 2: %2 = 2 -// 4: %3 = %1 + %2 -// 6: print(%3) -// Here the half-open life-time intervals would be: -// %1: (0-4] -// %2: (2-4] -// %3: (4-6] -// Instead, we use the even statement positions for uses of temporaries, and the odd positions for -// their definitions: -// %1: [1-4] -// %2: [3-4] -// %3: [5-6] -// This has the nice advantage that placing %3 (for example) is really easy: the start will -// never overlap with the end of the uses of the operands used in the defining statement. -// -// The reason to start a basic-block at firstStatementPosition - 1 is to have correct start -// positions for target temporaries of phi-nodes. Those temporaries will now start before the -// first statement. This also means that any moves that get generated when transforming out of SSA -// form, will not interfere with (read: overlap) any defining statements in the preceding -// basic-block. -void LifeTimeIntervals::renumber(IR::Function *function) -{ - for (BasicBlock *bb : function->basicBlocks()) { - if (bb->isRemoved()) - continue; - - _basicBlockPosition[bb->index()].start = _lastPosition + 1; - - for (Stmt *s : bb->statements()) { - if (s->asPhi()) - continue; - - _lastPosition += 2; - _positionForStatement[s->id()] = _lastPosition; - } - - _basicBlockPosition[bb->index()].end = _lastPosition; - } -} - -LifeTimeIntervals::~LifeTimeIntervals() -{ - qDeleteAll(_intervals); -} - -Optimizer::Optimizer(IR::Function *function) - : function(function) - , inSSA(false) -{} - -void Optimizer::run(QQmlEnginePrivate *qmlEngine, bool doTypeInference, bool peelLoops) -{ - showMeTheCode(function, "Before running the optimizer"); - - cleanupBasicBlocks(function); - - function->removeSharedExpressions(); - int statementCount = 0; - for (BasicBlock *bb : function->basicBlocks()) - if (!bb->isRemoved()) - statementCount += bb->statementCount(); -// showMeTheCode(function); - - static bool doSSA = qEnvironmentVariableIsEmpty("QV4_NO_SSA"); - - 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"); - - // Calculate the dominator tree: - DominatorTree df(function); - - { - // This is in a separate scope, because loop-peeling doesn't (yet) update the LoopInfo - // calculated by the LoopDetection. So by putting it in a separate scope, it is not - // available after peeling. - - LoopDetection loopDetection(df); - loopDetection.run(function); - showMeTheCode(function, "After loop detection"); -// cfg2dot(function, loopDetection.allLoops()); - - // ### disable loop peeling for now. It doesn't give any measurable performance - // improvements at this time, but significantly increases the size of the - // JIT generated code - Q_UNUSED(peelLoops); - if (0 && peelLoops) { - QVector<LoopDetection::LoopInfo *> innerLoops = loopDetection.innermostLoops(); - LoopPeeling(df).run(innerLoops); - -// cfg2dot(function, loopDetection.allLoops()); - showMeTheCode(function, "After loop peeling"); - if (!innerLoops.isEmpty()) - verifyImmediateDominators(df, function); - } - } - - verifyCFG(function); - verifyNoPointerSharing(function); - - df.computeDF(); - - verifyCFG(function); - verifyImmediateDominators(df, function); - - DefUses defUses(function); - -// qout << "Converting to SSA..." << endl; - convertToSSA(function, df, defUses); -// showMeTheCode(function); -// defUses.dump(); - -// qout << "Cleaning up phi nodes..." << endl; - cleanupPhis(defUses); - showMeTheCode(function, "After cleaning up phi-nodes"); - - StatementWorklist worklist(function); - - if (doTypeInference) { -// qout << "Running type inference..." << endl; - TypeInference(qmlEngine, defUses).run(worklist); - showMeTheCode(function, "After type inference"); - -// qout << "Doing reverse inference..." << endl; - ReverseInference(defUses).run(function); -// showMeTheCode(function); - -// qout << "Doing type propagation..." << endl; - TypePropagation(defUses).run(function, worklist); -// showMeTheCode(function); - verifyNoPointerSharing(function); - } - - static const bool doOpt = qEnvironmentVariableIsEmpty("QV4_NO_OPT"); - if (doOpt) { -// qout << "Running SSA optimization..." << endl; - worklist.reset(); - optimizeSSA(worklist, defUses, df); - showMeTheCode(function, "After optimization"); - - verifyImmediateDominators(df, function); - verifyCFG(function); - } - - verifyNoPointerSharing(function); - mergeBasicBlocks(function, &defUses, &df); - - verifyImmediateDominators(df, function); - verifyCFG(function); - - // 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 - // block scheduling, so remove those now. -// qout << "Cleaning up unreachable basic blocks..." << endl; - cleanupBasicBlocks(function); -// showMeTheCode(function); - - verifyImmediateDominators(df, function); - verifyCFG(function); - - // Transform the CFG into edge-split SSA. - showMeTheCode(function, "Before edge splitting"); - splitCriticalEdges(function, df, worklist, defUses); - showMeTheCode(function, "After edge splitting"); - - verifyImmediateDominators(df, function); - verifyCFG(function); - -// qout << "Doing block scheduling..." << endl; -// df.dumpImmediateDominators(); - startEndLoops = BlockScheduler(function, df).go(); - showMeTheCode(function, "After basic block scheduling"); -// cfg2dot(function); - -#ifndef QT_NO_DEBUG - checkCriticalEdges(function->basicBlocks()); -#endif - - if (!function->module->debugMode) { - RemoveLineNumbers::run(function); - showMeTheCode(function, "After line number removal"); - } - -// qout << "Finished SSA." << endl; - inSSA = true; - } else { - removeUnreachleBlocks(function); - inSSA = false; - } -} - -void Optimizer::convertOutOfSSA() { - if (!inSSA) - return; - - // There should be no critical edges at this point. - - for (BasicBlock *bb : function->basicBlocks()) { - MoveMapping moves; - - for (BasicBlock *successor : bb->out) { - const int inIdx = successor->in.indexOf(bb); - Q_ASSERT(inIdx >= 0); - for (Stmt *s : successor->statements()) { - if (Phi *phi = s->asPhi()) { - moves.add(clone(phi->incoming[inIdx], function), - clone(phi->targetTemp, function)->asTemp()); - } else { - break; - } - } - } - - if (DebugMoveMapping) { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream os(&buf); - os << "Move mapping for function "; - if (function->name) - os << *function->name; - else - os << (void *) function; - os << " on basic-block L" << bb->index() << ":" << endl; - moves.dump(); - buf.close(); - qDebug("%s", buf.data().constData()); - } - - moves.order(); - - moves.insertMoves(bb, function, true); - } - - for (BasicBlock *bb : function->basicBlocks()) { - while (!bb->isEmpty()) { - if (bb->statements().first()->asPhi()) { - bb->removeStatement(0); - } else { - break; - } - } - } -} - -LifeTimeIntervals::Ptr Optimizer::lifeTimeIntervals() const -{ - Q_ASSERT(isInSSA()); - - LifeRanges lifeRanges(function, startEndLoops); -// lifeRanges.dump(); -// showMeTheCode(function); - return lifeRanges.intervals(); -} - -static int countPhis(BasicBlock *bb) -{ - int count = 0; - for (Stmt *s : bb->statements()) { - if (s->isa<Phi>()) - ++count; - else - break; - } - - return count; -} - -// Basic blocks can have only 1 terminator. This function returns a bit vector, where a 1 on a -// certain index indicates that the terminator (jump) at the end of the basic block with that index -// can be omitted. -BitVector Optimizer::calculateOptionalJumps() -{ - const int maxSize = function->basicBlockCount(); - BitVector optional(maxSize, false); - if (maxSize < 2) - return optional; - - BitVector reachableWithoutJump(maxSize, false); - - for (int i = maxSize - 1; i >= 0; --i) { - BasicBlock *bb = function->basicBlock(i); - if (bb->isRemoved()) - continue; - - if (Jump *jump = bb->statements().last()->asJump()) { - if (reachableWithoutJump.at(jump->target->index())) { - if (bb->statements().size() - countPhis(bb)> 1) - reachableWithoutJump.clear(); - optional.setBit(bb->index()); - reachableWithoutJump.setBit(bb->index()); - continue; - } - } - - reachableWithoutJump.clear(); - reachableWithoutJump.setBit(bb->index()); - } - - return optional; -} - -void Optimizer::showMeTheCode(IR::Function *function, const char *marker) -{ - ::showMeTheCode(function, marker); -} - -static inline bool overlappingStorage(const Temp &t1, const Temp &t2) -{ - // This is the same as the operator==, but for one detail: memory locations are not sensitive - // to types, and neither are general-purpose registers. - - if (t1.index != t2.index) - return false; // different position, where-ever that may (physically) be. - if (t1.kind != t2.kind) - return false; // formal/local/(physical-)register/stack do never overlap - if (t1.kind != Temp::PhysicalRegister) // Other than registers, ... - return t1.kind == t2.kind; // ... everything else overlaps: any memory location can hold everything. - - // So now the index is the same, and we know that both stored in a register. If both are - // floating-point registers, they are the same. Or, if both are non-floating-point registers, - // generally called general-purpose registers, they are also the same. - return (t1.type == DoubleType && t2.type == DoubleType) - || (t1.type != DoubleType && t2.type != DoubleType); -} - -MoveMapping::Moves MoveMapping::sourceUsages(Expr *e, const Moves &moves) -{ - Moves usages; - - if (Temp *t = e->asTemp()) { - for (int i = 0, ei = moves.size(); i != ei; ++i) { - const Move &move = moves[i]; - if (Temp *from = move.from->asTemp()) - if (overlappingStorage(*from, *t)) - usages.append(move); - } - } - - return usages; -} - -void MoveMapping::add(Expr *from, Temp *to) { - if (Temp *t = from->asTemp()) { - if (overlappingStorage(*t, *to)) { - // assignments like fp1 = fp1 or var{&1} = double{&1} can safely be skipped. - if (DebugMoveMapping) { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream os(&buf); - IRPrinter printer(&os); - os << "Skipping "; - printer.print(to); - os << " <- "; - printer.print(from); - buf.close(); - qDebug("%s", buf.data().constData()); - } - return; - } - } - - Move m(from, to); - if (_moves.contains(m)) - return; - _moves.append(m); -} - -// Order the moves that are generated when resolving edges during register allocation (see [Wimmer1] -// section 6 for details). Now these moves form one or more graphs, so we have to output them in -// such an order that values don't get overwritten: -// r1 <- r0 -// r2 <- r1 -// That input has to be ordered as follows in order to prevent the value in r1 from being lost: -// r2 <- r1 -// r1 <- r0 -// -// So, the algorithm is to output the leaves first, and take them out of the input. This will result -// in some moves to become leaves (in the above example: when leaf r2 <- r1 is generated and taken -// away, the r1 <- r0 is now a leaf), so we can output those and take those out, and repeat until -// there are no more leafs. -// -// The tricky part is that there might be cycles: -// r4 <- r5 -// r5 <- r4 -// These have to be turned into a "register swap": -// r4 <=> r5 -// -// So after running the above algorithm where we progressively remove the leaves, we are left with -// zero or more cycles. To resolve those, we break one of the edges of the cycle, and for all other -// edges we generate swaps. Note that the swaps will always occur as the last couple of moves, -// because otherwise they might clobber sources for moves: -// r4 <=> r5 -// r6 <- r5 -// Here, the value of r5 is already overwritten with the one in r4, so the correct order is: -// r6 <- r5 -// r4 <=> r5 -void MoveMapping::order() -{ - QList<Move> output; - output.reserve(_moves.size()); - - while (!_moves.isEmpty()) { - // Take out all leaf edges, because we can output them without any problems. - int nextLeaf = findLeaf(); - if (nextLeaf == -1) - break; // No more leafs left, we're done here. - output.append(_moves.takeAt(nextLeaf)); - // Now there might be new leaf edges: any move that had the input of the previously found - // leaf as an output, so loop around. - } - - while (!_moves.isEmpty()) { - // We're now left with one or more cycles. - // Step one: break the/a cycle. - _moves.removeFirst(); - // Step two: find the other edges of the cycle, starting with the one of that is now a leaf. - while (!_moves.isEmpty()) { - int nextLeaf = findLeaf(); - if (nextLeaf == -1) - break; // We're done with this cycle. - Move m = _moves.takeAt(nextLeaf); - // Step three: get the edges from the cycle and turn it into a swap - m.needsSwap = true; - output.append(m); - // Because we took out a leaf, find the next one. - } - // We're done with the cycle, let's see if there are more. - } - - _moves = output; -} - -int MoveMapping::findLeaf() const -{ - for (int i = 0, e = _moves.size(); i != e; ++i) { - // Take an edge from the list... - const Temp *target = _moves.at(i).to; - // ... and see if its target is used as a source... - bool targetUsedAsSource = false; - for (int j = 0; j != e; ++j) { - if (i == j) - continue; - - Expr *source = _moves.at(j).from; - if (const Temp *sourceTemp = source->asTemp()) { - if (overlappingStorage(*target, *sourceTemp)) { - targetUsedAsSource = true; - break; - } - } - } - // ... if not, we have a leaf edge ... - if (!targetUsedAsSource) - return i; - // .. otherwise we try the next one. - } - - return -1; // No leaf found -} - -QList<IR::Move *> MoveMapping::insertMoves(BasicBlock *bb, IR::Function *function, bool atEnd) const -{ - QList<IR::Move *> newMoves; - newMoves.reserve(_moves.size()); - - int insertionPoint = atEnd ? bb->statements().size() - 1 : 0; - for (const Move &m : _moves) { - IR::Move *move = function->NewStmt<IR::Move>(); - move->init(clone(m.to, function), clone(m.from, function)); - move->swap = m.needsSwap; - bb->insertStatementBefore(insertionPoint++, move); - newMoves.append(move); - } - - return newMoves; -} - -void MoveMapping::dump() const -{ - if (DebugMoveMapping) { - QBuffer buf; - buf.open(QIODevice::WriteOnly); - QTextStream os(&buf); - IRPrinter printer(&os); - os << "Move mapping has " << _moves.size() << " moves..." << endl; - for (const Move &m : _moves) { - os << "\t"; - printer.print(m.to); - if (m.needsSwap) - os << " <-> "; - else - os << " <-- "; - printer.print(m.from); - os << endl; - } - qDebug("%s", buf.data().constData()); - } -} - -// References: -// [Wimmer1] C. Wimmer and M. Franz. Linear Scan Register Allocation on SSA Form. In Proceedings of -// CGO'10, ACM Press, 2010 -// [Wimmer2] C. Wimmer and H. Mossenbock. Optimized Interval Splitting in a Linear Scan Register -// Allocator. In Proceedings of the ACM/USENIX International Conference on Virtual -// Execution Environments, pages 132-141. ACM Press, 2005. -// [Briggs] P. Briggs, K.D. Cooper, T.J. Harvey, and L.T. Simpson. Practical Improvements to the -// Construction and Destruction of Static Single Assignment Form. -// [Appel] A.W. Appel. Modern Compiler Implementation in Java. Second edition, Cambridge -// University Press. -// [Ramalingam] G. Ramalingam and T. Reps. An Incremental Algorithm for Maintaining the Dominator -// Tree of a Reducible Flowgraph. diff --git a/src/qml/compiler/qv4ssa_p.h b/src/qml/compiler/qv4ssa_p.h deleted file mode 100644 index 24257e99e9..0000000000 --- a/src/qml/compiler/qv4ssa_p.h +++ /dev/null @@ -1,472 +0,0 @@ -/**************************************************************************** -** -** 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 QV4SSA_P_H -#define QV4SSA_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 "qv4jsir_p.h" -#include "qv4isel_util_p.h" -#include <private/qv4util_p.h> -#include <QtCore/QSharedPointer> - -QT_BEGIN_NAMESPACE -class QTextStream; -class QQmlEnginePrivate; - -namespace QV4 { -namespace IR { - -struct LifeTimeIntervalRange { - int start; - int end; - - LifeTimeIntervalRange(int start = -1, int end = -1) - : start(start) - , end(end) - {} - - bool covers(int position) const { return start <= position && position <= end; } -}; -} // IR namespace -} // QV4 namespace - -Q_DECLARE_TYPEINFO(QV4::IR::LifeTimeIntervalRange, Q_PRIMITIVE_TYPE); - -namespace QV4 { -namespace IR { - -class Q_AUTOTEST_EXPORT LifeTimeInterval { -public: - typedef QVarLengthArray<LifeTimeIntervalRange, 4> Ranges; - -private: - Temp _temp; - Ranges _ranges; - int _end; - int _reg; - unsigned _isFixedInterval : 1; - unsigned _isSplitFromInterval : 1; - -public: - enum { InvalidPosition = -1 }; - enum { InvalidRegister = -1 }; - - explicit LifeTimeInterval(int rangeCapacity = 4) - : _end(InvalidPosition) - , _reg(InvalidRegister) - , _isFixedInterval(0) - , _isSplitFromInterval(0) - { _ranges.reserve(rangeCapacity); } - - bool isValid() const { return _end != InvalidRegister; } - - void setTemp(const Temp &temp) { this->_temp = temp; } - Temp temp() const { return _temp; } - bool isFP() const { return _temp.type == IR::DoubleType; } - - void setFrom(int from); - void addRange(int from, int to); - const Ranges &ranges() const { return _ranges; } - - int start() const { return _ranges.first().start; } - int end() const { return _end; } - bool covers(int position) const - { - for (int i = 0, ei = _ranges.size(); i != ei; ++i) { - if (_ranges.at(i).covers(position)) - return true; - } - return false; - } - - int reg() const { return _reg; } - void setReg(int reg) { Q_ASSERT(!_isFixedInterval); _reg = reg; } - - bool isFixedInterval() const { return _isFixedInterval; } - void setFixedInterval(bool isFixedInterval) { _isFixedInterval = isFixedInterval; } - - LifeTimeInterval split(int atPosition, int newStart); - bool isSplitFromInterval() const { return _isSplitFromInterval; } - void setSplitFromInterval(bool isSplitFromInterval) { _isSplitFromInterval = isSplitFromInterval; } - - void dump(QTextStream &out) const; - static bool lessThan(const LifeTimeInterval *r1, const LifeTimeInterval *r2); - static bool lessThanForTemp(const LifeTimeInterval *r1, const LifeTimeInterval *r2); - - void validate() const { -#if !defined(QT_NO_DEBUG) - // Validate the new range - if (_end != InvalidPosition) { - Q_ASSERT(!_ranges.isEmpty()); - for (const LifeTimeIntervalRange &range : qAsConst(_ranges)) { - Q_ASSERT(range.start >= 0); - Q_ASSERT(range.end >= 0); - Q_ASSERT(range.start <= range.end); - } - } -#endif - } -}; - -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) - - LifeTimeIntervals(IR::Function *function); - void renumber(IR::Function *function); - -public: - typedef QSharedPointer<LifeTimeIntervals> Ptr; - static Ptr create(IR::Function *function) - { return Ptr(new LifeTimeIntervals(function)); } - - ~LifeTimeIntervals(); - - // takes ownership of the pointer - void add(LifeTimeInterval *interval) - { _intervals.append(interval); } - - // After calling Optimizer::lifeTimeIntervals() the result will have all intervals in descending order of start position. - QVector<LifeTimeInterval *> intervals() const - { return _intervals; } - - int size() const - { return _intervals.size(); } - - int positionForStatement(Stmt *stmt) const - { - Q_ASSERT(stmt->id() >= 0); - if (static_cast<unsigned>(stmt->id()) < _positionForStatement.size()) - return _positionForStatement[stmt->id()]; - - return Stmt::InvalidId; - } - - int startPosition(BasicBlock *bb) const - { - Q_ASSERT(bb->index() >= 0); - Q_ASSERT(static_cast<unsigned>(bb->index()) < _basicBlockPosition.size()); - - return _basicBlockPosition.at(bb->index()).start; - } - - int endPosition(BasicBlock *bb) const - { - Q_ASSERT(bb->index() >= 0); - Q_ASSERT(static_cast<unsigned>(bb->index()) < _basicBlockPosition.size()); - - return _basicBlockPosition.at(bb->index()).end; - } - - int lastPosition() const - { - return _lastPosition; - } - -private: - struct BasicBlockPositions { - int start; - int end; - - BasicBlockPositions() - : start(IR::Stmt::InvalidId) - , end(IR::Stmt::InvalidId) - {} - }; - - std::vector<BasicBlockPositions> _basicBlockPosition; - std::vector<int> _positionForStatement; - QVector<LifeTimeInterval *> _intervals; - int _lastPosition; -}; - -class Q_QML_PRIVATE_EXPORT Optimizer -{ - Q_DISABLE_COPY(Optimizer) - -public: - Optimizer(Function *function); - - void run(QQmlEnginePrivate *qmlEngine, bool doTypeInference = true, bool peelLoops = true); - void convertOutOfSSA(); - - bool isInSSA() const - { return inSSA; } - - QHash<BasicBlock *, BasicBlock *> loopStartEndBlocks() const { return startEndLoops; } - - LifeTimeIntervals::Ptr lifeTimeIntervals() const; - - BitVector calculateOptionalJumps(); - - static void showMeTheCode(Function *function, const char *marker); - -private: - Function *function; - bool inSSA; - QHash<BasicBlock *, BasicBlock *> startEndLoops; -}; - -class Q_AUTOTEST_EXPORT MoveMapping -{ -#ifdef V4_AUTOTEST -public: -#endif - struct Move { - Expr *from; - Temp *to; - bool needsSwap; - - Move(Expr *from, Temp *to, bool needsSwap = false) - : from(from), to(to), needsSwap(needsSwap) - {} - - bool operator==(const Move &other) const - { return from == other.from && to == other.to; } - }; - typedef QList<Move> Moves; - - Moves _moves; - - static Moves sourceUsages(Expr *e, const Moves &moves); - -public: - void add(Expr *from, Temp *to); - void order(); - QList<IR::Move *> insertMoves(BasicBlock *bb, Function *function, bool atEnd) const; - - void dump() const; - -private: - int findLeaf() const; -}; - -/* - * stack slot allocation: - * - * foreach bb do - * foreach stmt do - * if the current statement is not a phi-node: - * purge ranges that end before the current statement - * check for life ranges to activate, and if they don't have a stackslot associated then allocate one - * renumber temps to stack - * for phi nodes: check if all temps (src+dst) are assigned stack slots and marked as allocated - * if it's a jump: - * foreach phi node in the successor: - * allocate slots for each temp (both sources and targets) if they don't have one allocated already - * insert moves before the jump - */ -class AllocateStackSlots: protected ConvertTemps -{ - IR::LifeTimeIntervals::Ptr _intervals; - QVector<IR::LifeTimeInterval *> _unhandled; - QVector<IR::LifeTimeInterval *> _live; - QBitArray _slotIsInUse; - IR::Function *_function; - - int defPosition(IR::Stmt *s) const - { - return usePosition(s) + 1; - } - - int usePosition(IR::Stmt *s) const - { - return _intervals->positionForStatement(s); - } - -public: - AllocateStackSlots(const IR::LifeTimeIntervals::Ptr &intervals) - : _intervals(intervals) - , _slotIsInUse(intervals->size(), false) - , _function(0) - { - _live.reserve(8); - _unhandled = _intervals->intervals(); - } - - void forFunction(IR::Function *function) - { - IR::Optimizer::showMeTheCode(function, "Before stack slot allocation"); - _function = function; - toStackSlots(function); - } - -protected: - int allocateFreeSlot() override - { - for (int i = 0, ei = _slotIsInUse.size(); i != ei; ++i) { - if (!_slotIsInUse[i]) { - if (_nextUnusedStackSlot <= i) { - Q_ASSERT(_nextUnusedStackSlot == i); - _nextUnusedStackSlot = i + 1; - } - _slotIsInUse[i] = true; - return i; - } - } - - Q_UNREACHABLE(); - return -1; - } - - void process(IR::Stmt *s) override - { -// qDebug("L%d statement %d:", _currentBasicBlock->index, s->id); - - if (IR::Phi *phi = s->asPhi()) { - visitPhi(phi); - } else { - // purge ranges no longer alive: - for (int i = 0; i < _live.size(); ) { - const IR::LifeTimeInterval *lti = _live.at(i); - if (lti->end() < usePosition(s)) { -// qDebug() << "\t - moving temp" << lti->temp().index << "to handled, freeing slot" << _stackSlotForTemp[lti->temp().index]; - _live.remove(i); - Q_ASSERT(_slotIsInUse[_stackSlotForTemp[lti->temp().index]]); - _slotIsInUse[_stackSlotForTemp[lti->temp().index]] = false; - continue; - } else { - ++i; - } - } - - // active new ranges: - while (!_unhandled.isEmpty()) { - IR::LifeTimeInterval *lti = _unhandled.last(); - if (lti->start() > defPosition(s)) - break; // we're done - Q_ASSERT(!_stackSlotForTemp.contains(lti->temp().index)); - _stackSlotForTemp[lti->temp().index] = allocateFreeSlot(); -// qDebug() << "\t - activating temp" << lti->temp().index << "on slot" << _stackSlotForTemp[lti->temp().index]; - _live.append(lti); - _unhandled.removeLast(); - } - - visit(s); - } - - if (IR::Jump *jump = s->asJump()) { - IR::MoveMapping moves; - for (IR::Stmt *succStmt : jump->target->statements()) { - if (IR::Phi *phi = succStmt->asPhi()) { - forceActivation(*phi->targetTemp); - for (int i = 0, ei = phi->incoming.size(); i != ei; ++i) { - IR::Expr *e = phi->incoming[i]; - if (IR::Temp *t = e->asTemp()) { - forceActivation(*t); - } - if (jump->target->in[i] == _currentBasicBlock) - moves.add(phi->incoming[i], phi->targetTemp); - } - } else { - break; - } - } - moves.order(); - const QList<IR::Move *> newMoves = moves.insertMoves(_currentBasicBlock, _function, true); - for (IR::Move *move : newMoves) - visit(move); - } - } - - void forceActivation(const IR::Temp &t) - { - if (_stackSlotForTemp.contains(t.index)) - return; - - int i = _unhandled.size() - 1; - for (; i >= 0; --i) { - IR::LifeTimeInterval *lti = _unhandled[i]; - if (lti->temp() == t) { - _live.append(lti); - _unhandled.remove(i); - break; - } - } - Q_ASSERT(i >= 0); // check that we always found the entry - - _stackSlotForTemp[t.index] = allocateFreeSlot(); -// qDebug() << "\t - force activating temp" << t.index << "on slot" << _stackSlotForTemp[t.index]; - } - - void visitPhi(IR::Phi *phi) override - { - Q_UNUSED(phi); -#if !defined(QT_NO_DEBUG) - Q_ASSERT(_stackSlotForTemp.contains(phi->targetTemp->index)); - Q_ASSERT(_slotIsInUse[_stackSlotForTemp[phi->targetTemp->index]]); - for (IR::Expr *e : phi->incoming) { - if (IR::Temp *t = e->asTemp()) - Q_ASSERT(_stackSlotForTemp.contains(t->index)); - } -#endif // defined(QT_NO_DEBUG) - } -}; - -} // IR namespace -} // QV4 namespace - - -Q_DECLARE_TYPEINFO(QV4::IR::LifeTimeInterval, Q_MOVABLE_TYPE); - -QT_END_NAMESPACE - -#endif // QV4SSA_P_H |