diff options
author | Simon Hausmann <simon.hausmann@qt.io> | 2018-07-10 14:52:34 +0200 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@qt.io> | 2018-08-09 13:18:39 +0000 |
commit | 82da798499aa8b656e771191332864a703069739 (patch) | |
tree | 35cb1d0ef8dd3d949f8b6f6324d19ec577b3f4df /src/qml/compiler | |
parent | 6510046ee32ef69d7f250fd1d829063983f93fdd (diff) |
Add initial basic support for ES6 modules
The entry point from the parsing perspective into modules is not
QV4::Script but QV4::ExecutionEngine::compileModule.
For convenience, the ESModule AST node gets a body, which is the
statement list connected between the ModuleItemList items that are not
import/export declarations.
The QV4::Module allocates a call context where the exported variables
are stored as named locals. This will also become the module namespace
object.
The imports in turn is an array of value pointers that point into the
locals array of the context of the imported modules.
The default module loading in ExecutionEngine assumes the accessibility
of module urls via QFile (so local file system or resource). This is
what qmljs also uses and QJSEngine as well via public API in the future.
The test runner compiles the modules manually and injects them, because
they need to be compiled together with the test harness code.
The QML type loader will the mechanism for injection in the future for
module imports from .qml files.
Change-Id: I93be9cfe54c651fdbd08c5e1d22d58f47284e54f
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/qml/compiler')
-rw-r--r-- | src/qml/compiler/qv4bytecodegenerator_p.h | 3 | ||||
-rw-r--r-- | src/qml/compiler/qv4bytecodehandler.cpp | 3 | ||||
-rw-r--r-- | src/qml/compiler/qv4codegen.cpp | 70 | ||||
-rw-r--r-- | src/qml/compiler/qv4codegen_p.h | 12 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata.cpp | 117 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata_p.h | 48 | ||||
-rw-r--r-- | src/qml/compiler/qv4compiler.cpp | 62 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilercontext.cpp | 14 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilercontext_p.h | 30 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilerscanfunctions.cpp | 129 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilerscanfunctions_p.h | 6 | ||||
-rw-r--r-- | src/qml/compiler/qv4instr_moth.cpp | 4 | ||||
-rw-r--r-- | src/qml/compiler/qv4instr_moth_p.h | 2 |
13 files changed, 491 insertions, 9 deletions
diff --git a/src/qml/compiler/qv4bytecodegenerator_p.h b/src/qml/compiler/qv4bytecodegenerator_p.h index dca5771356..4f3dc27acc 100644 --- a/src/qml/compiler/qv4bytecodegenerator_p.h +++ b/src/qml/compiler/qv4bytecodegenerator_p.h @@ -163,8 +163,11 @@ public: 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() diff --git a/src/qml/compiler/qv4bytecodehandler.cpp b/src/qml/compiler/qv4bytecodehandler.cpp index 23f7051718..d3c5582b28 100644 --- a/src/qml/compiler/qv4bytecodehandler.cpp +++ b/src/qml/compiler/qv4bytecodehandler.cpp @@ -161,6 +161,9 @@ std::vector<int> ByteCodeHandler::collectLabelsInBytecode(const char *code, uint COLLECTOR_BEGIN_INSTR(MoveConst) COLLECTOR_END_INSTR(MoveConst) + COLLECTOR_BEGIN_INSTR(LoadImport) + COLLECTOR_END_INSTR(LoadImport) + COLLECTOR_BEGIN_INSTR(LoadLocal) COLLECTOR_END_INSTR(LoadLocal) diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 30a73d31bd..7b059b3fb4 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -126,6 +126,49 @@ void Codegen::generateFromProgram(const QString &fileName, defineFunction(QStringLiteral("%entry"), node, nullptr, node->statements); } +void Codegen::generateFromModule(const QString &fileName, + const QString &finalUrl, + const QString &sourceCode, + ESModule *node, + Module *module) +{ + Q_ASSERT(node); + + _module = module; + _context = nullptr; + + // ### should be set on the module outside of this method + _module->fileName = fileName; + _module->finalUrl = finalUrl; + + ScanFunctions scan(this, sourceCode, ContextType::ESModule); + scan(node); + + if (hasError) + return; + + { + 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; + } + + 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); + + defineFunction(QStringLiteral("%entry"), node, nullptr, node->statements); +} + void Codegen::enterContext(Node *node) { _context = _module->contextMap.value(node); @@ -2163,13 +2206,21 @@ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs) { Context::ResolvedName resolved = _context->resolveName(name); - if (resolved.type == Context::ResolvedName::Local || resolved.type == Context::ResolvedName::Stack) { + 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 = (resolved.type == Context::ResolvedName::Local) ? - Reference::fromScopedLocal(this, resolved.index, resolved.scope) : - Reference::fromStackSlot(this, resolved.index, true /*isLocal*/); + 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; @@ -3744,6 +3795,9 @@ Codegen::Reference &Codegen::Reference::operator =(const Reference &other) elementBase = other.elementBase; elementSubscript = other.elementSubscript; break; + case Import: + index = other.index; + break; case Const: constant = other.constant; break; @@ -3789,6 +3843,8 @@ bool Codegen::Reference::operator==(const Codegen::Reference &other) const 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: @@ -4031,6 +4087,7 @@ void Codegen::Reference::storeAccumulator() const case Invalid: case Accumulator: case Const: + case Import: break; } @@ -4145,6 +4202,11 @@ QT_WARNING_POP codegen->bytecodeGenerator->addInstruction(load); } return; + case Import: { + Instruction::LoadImport load; + load.index = index; + codegen->bytecodeGenerator->addInstruction(load); + } return; case Subscript: { elementSubscript.loadInAccumulator(); Instruction::LoadElement load; diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 92f8b0b13d..e3617254a7 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -104,6 +104,12 @@ public: Module *module, ContextType contextType = ContextType::Global); + void generateFromModule(const QString &fileName, + const QString &finalUrl, + const QString &sourceCode, + AST::ESModule *ast, + Module *module); + public: class VolatileMemoryLocationScanner; class VolatileMemoryLocations { @@ -186,6 +192,7 @@ public: Name, Member, Subscript, + Import, QmlScopeObject, QmlContextObject, LastLValue = QmlContextObject, @@ -273,6 +280,11 @@ public: 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; diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 00a36cbb46..8e46ebf230 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -50,6 +50,7 @@ #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> @@ -113,6 +114,9 @@ CompilationUnit::~CompilationUnit() delete [] constants; constants = nullptr; #endif + + delete [] imports; + imports = nullptr; } QString CompilationUnit::localCacheFilePath(const QUrl &url) @@ -291,6 +295,9 @@ void CompilationUnit::markObjects(QV4::MarkStack *markStack) for (uint i = 0; i < data->lookupTableSize; ++i) runtimeLookups[i].markObjects(markStack); } + + if (m_module) + m_module->mark(markStack); } IdentifierHash CompilationUnit::createNamedObjectsPerComponent(int componentObjectIndex) @@ -370,6 +377,116 @@ bool CompilationUnit::verifyChecksum(const DependentTypesHasher &dependencyHashe sizeof(data->dependencyMD5Checksum)) == 0; } +QStringList CompilationUnit::moduleRequests() const +{ + QStringList requests; + + for (uint i = 0; i < data->importEntryTableSize; ++i) { + const ImportEntry &entry = data->importEntryTable()[i]; + requests << stringAt(entry.moduleRequest); + } + + return requests; +} + +Heap::Module *CompilationUnit::instantiate(ExecutionEngine *engine) +{ + if (m_module) + return m_module; + + if (!this->engine) + linkToEngine(engine); + + m_module = engine->memoryManager->allocate<Module>(engine, this); + + for (const QString &request: moduleRequests()) { + auto dependentModuleUnit = engine->loadModule(QUrl(request), this); + if (engine->hasException) + return nullptr; + dependentModuleUnit->instantiate(engine); + } + + Scope scope(engine); + 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(), /*### line*/1, /*### column*/1); + return nullptr; + } + imports[i] = valuePtr; + } + + return m_module; +} + +const Value *CompilationUnit::resolveExport(QV4::String *exportName) +{ + if (!m_module) + return nullptr; + + Scope scope(engine); + ScopedString localName(scope, localNameForExportName(exportName)); + if (!localName) + return nullptr; + + uint index = m_module->scope->internalClass->find(localName->toPropertyKey()); + if (index < UINT_MAX) + return &m_module->scope->locals[index]; + + return nullptr; +} + +Heap::String *CompilationUnit::localNameForExportName(QV4::String *exportName) const +{ + const CompiledData::ExportEntry *firstExport = data->localExportEntryTable(); + const CompiledData::ExportEntry *lastExport = data->localExportEntryTable() + data->localExportEntryTableSize; + auto matchingExport = std::lower_bound(firstExport, lastExport, exportName, [this](const CompiledData::ExportEntry &lhs, QV4::String *name) { + return stringAt(lhs.exportName) < name->toQString(); + }); + if (matchingExport == lastExport || stringAt(matchingExport->exportName) != exportName->toQString()) + return nullptr; + return runtimeStrings[matchingExport->localName]; +} + +void CompilationUnit::evaluate() +{ + if (m_moduleEvaluated) + return; + m_moduleEvaluated = true; + + for (const QString &request: moduleRequests()) { + auto dependentModuleUnit = engine->loadModule(QUrl(request), this); + if (engine->hasException) + return; + dependentModuleUnit->evaluate(); + if (engine->hasException) + return; + } + + QV4::Function *moduleFunction = runtimeFunctions[data->indexOfRootFunction]; + CppStackFrame frame; + frame.init(engine, moduleFunction, nullptr, 0); + frame.setupJSFrame(engine->jsStackTop, Primitive::undefinedValue(), m_module->scope, + Primitive::undefinedValue(), Primitive::undefinedValue()); + + frame.push(); + engine->jsStackTop += frame.requiredJSStackFrameSize(); + auto frameCleanup = qScopeGuard([&frame]() { + frame.pop(); + }); + Moth::VME::exec(&frame, engine); +} + bool CompilationUnit::loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString) { if (!QQmlFile::isLocalFile(url)) { diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index e7d152c7f8..e65c04ad69 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -88,6 +88,10 @@ struct Document; namespace QV4 { +namespace Heap { +struct Module; +}; + struct Function; class EvalISelFactory; class CompilationUnitMapper; @@ -358,6 +362,23 @@ struct Class }; 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 ExportEntry +{ + quint32_le exportName; + quint32_le moduleRequest; + quint32_le importName; + quint32_le localName; +}; +static_assert(sizeof(ExportEntry) == 16, "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; + quint32_le padding; +}; +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 @@ -819,6 +840,14 @@ struct Unit 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; qint32_le indexOfRootFunction; quint32_le sourceFileIndex; quint32_le finalUrlIndex; @@ -904,9 +933,14 @@ struct Unit 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); } }; -static_assert(sizeof(Unit) == 200, "Unit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +static_assert(sizeof(Unit) == 232, "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 { @@ -991,12 +1025,15 @@ struct Q_QML_PRIVATE_EXPORT CompilationUnitBase 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 final : public CompilationUnitBase { @@ -1122,6 +1159,12 @@ public: FunctionIterator objectFunctionsEnd(const Object *object) const { return FunctionIterator(data, object, object->nFunctions); } // --- + QStringList moduleRequests() const; + Heap::Module *instantiate(ExecutionEngine *engine); + const Value *resolveExport(QV4::String *exportName); + Heap::String *localNameForExportName(QV4::String *exportName) const; + void evaluate(); + QV4::Function *linkToEngine(QV4::ExecutionEngine *engine); void unlink(); @@ -1149,6 +1192,9 @@ private: Q_NEVER_INLINE IdentifierHash createNamedObjectsPerComponent(int componentObjectIndex); + Heap::Module *m_module = nullptr; + bool m_moduleEvaluated = false; + public: #if defined(V4_BOOTSTRAP) bool saveToDisk(const QString &outputFileName, QString *errorString); diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index f301f867c1..4e902eca65 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -241,6 +241,24 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO 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); + } + } Q_ALLOCA_VAR(quint32_le, blockClassAndFunctionOffsets, (module->functions.size() + module->classes.size() + module->blocks.size()) * sizeof(quint32_le)); uint jsClassDataOffset = 0; @@ -304,8 +322,35 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO 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++; + } + }; + 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++; + } + } + // write strings and string table if (option == GenerateWithStringTable) stringTable.serialize(unit); @@ -547,8 +592,23 @@ QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Comp nextOffset = (nextOffset + 7) & ~quint32(0x7); - quint32 functionSize = 0; + const auto reserveExportTable = [&nextOffset](int count, quint32_le *tableSizePtr, quint32_le *offsetPtr) { + *tableSizePtr = count; + *offsetPtr = nextOffset; + nextOffset += count * sizeof(CompiledData::ExportEntry); + nextOffset = (nextOffset + 7) & ~quint32(0x7); + }; + 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 = (nextOffset + 7) & ~quint32(0x7); + + quint32 functionSize = 0; for (int i = 0; i < module->functions.size(); ++i) { Context *f = module->functions.at(i); blockAndFunctionOffsets[i] = nextOffset; diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index 9dfe3be7e0..77ac703ee3 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -156,6 +156,17 @@ Context::ResolvedName Context::resolveName(const QString &name) 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; + return result; + } + } + } + // ### can we relax the restrictions here? if (contextType == ContextType::Eval || c->contextType == ContextType::Binding) return result; @@ -219,7 +230,7 @@ void Context::emitBlockHeader(Codegen *codegen) } } - if (contextType == ContextType::Function || contextType == ContextType::Binding) { + 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 @@ -285,6 +296,7 @@ void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator) registerOffset = bytecodeGenerator->currentRegister(); switch (contextType) { + case ContextType::ESModule: case ContextType::Block: case ContextType::Function: case ContextType::Binding: { diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h index 52c3fc5b05..6a54be2aca 100644 --- a/src/qml/compiler/qv4compilercontext_p.h +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -74,7 +74,8 @@ enum class ContextType { // * 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 + Block, + ESModule }; struct Context; @@ -97,6 +98,24 @@ struct Class { QVector<Method> methods; }; +struct ExportEntry +{ + QString exportName; + QString moduleRequest; + QString importName; + QString localName; + + static bool lessThan(const ExportEntry &lhs, const ExportEntry &rhs) + { return lhs.exportName < rhs.exportName; } +}; + +struct ImportEntry +{ + QString moduleRequest; + QString importName; + QString localName; +}; + struct Module { Module(bool debugMode) : debugMode(debugMode) @@ -117,6 +136,10 @@ struct Module { 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; }; @@ -153,6 +176,8 @@ struct Context { QQmlJS::AST::FormalParameterList *formals = nullptr; QStringList arguments; QStringList locals; + QVector<ImportEntry> importEntries; + QVector<ExportEntry> exportEntries; QVector<Context *> nestedContexts; ControlFlow *controlFlow = nullptr; @@ -289,7 +314,8 @@ struct Context { Unresolved, Global, Local, - Stack + Stack, + Import }; Type type = Unresolved; bool isArgOrEval = false; diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index 9be55c6ad0..63ed748048 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -149,6 +149,120 @@ 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 (declaration->exportAll) { + Compiler::ExportEntry entry; + entry.moduleRequest = declaration->fromClause->moduleSpecifier.toString(); + entry.importName = QStringLiteral("*"); + _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(); + + _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; + _context->exportEntries << entry; + } + } else if (auto *classDecl = AST::cast<AST::ClassDeclaration*>(declaration->variableStatementOrDeclaration)) { + QString name = classDecl->name.toString(); + Compiler::ExportEntry entry; + entry.localName = name; + entry.exportName = name; + _context->exportEntries << entry; + } else if (auto *fdef = declaration->variableStatementOrDeclaration->asFunctionDefinition()) { + QString name = fdef->name.toString(); + Compiler::ExportEntry entry; + entry.localName = name; + entry.exportName = name; + _context->exportEntries << entry; + } else if (declaration->exportDefault) { + Compiler::ExportEntry entry; + entry.localName = QStringLiteral("*default*"); + entry.exportName = QStringLiteral("default"); + _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 (ImportClause *import = declaration->importClause) { + if (!import->importedDefaultBinding.isEmpty()) { + Compiler::ImportEntry entry; + entry.moduleRequest = module; + entry.importName = QStringLiteral("default"); + entry.localName = import->importedDefaultBinding.toString(); + _context->importEntries << entry; + } + + if (import->nameSpaceImport) { + Compiler::ImportEntry entry; + entry.moduleRequest = module; + entry.importName = QStringLiteral("*"); + entry.localName = import->nameSpaceImport->importedBinding.toString(); + _context->importEntries << entry; + + _cg->throwSyntaxError(import->nameSpaceImport->importedBindingToken, QStringLiteral("* imports are currently not supported.")); + return false; + } + + 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; + _context->importEntries << entry; + } + } + } + return false; +} + bool ScanFunctions::visit(CallExpression *ast) { if (!_context->hasDirectEval) { @@ -529,6 +643,17 @@ void ScanFunctions::calcEscapingVariables() } } + 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; @@ -545,6 +670,10 @@ void ScanFunctions::calcEscapingVariables() 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; } diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index 4c273600b3..53b2336cb1 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -103,6 +103,12 @@ protected: 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; diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index 8e474b3783..d186c4e7e6 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -207,6 +207,10 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st 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) diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index ce92a31590..25a07208c2 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -76,6 +76,7 @@ QT_BEGIN_NAMESPACE #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, 1, index) #define INSTR_StoreLocal(op) INSTRUCTION(op, StoreLocal, 1, index) #define INSTR_LoadScopedLocal(op) INSTRUCTION(op, LoadScopedLocal, 2, scope, index) @@ -210,6 +211,7 @@ QT_BEGIN_NAMESPACE F(LoadReg) \ F(StoreReg) \ F(MoveReg) \ + F(LoadImport) \ F(LoadLocal) \ F(StoreLocal) \ F(LoadScopedLocal) \ |