diff options
26 files changed, 418 insertions, 152 deletions
diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp index c33d0026d5..99d106d5f6 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp @@ -81,8 +81,9 @@ QV4::Heap::CallContext *QV4DataCollector::findScope(QV4::Heap::ExecutionContext for (; scope > 0 && ctx; --scope) ctx = ctx->outer; - return (ctx && ctx->type == QV4::Heap::ExecutionContext::Type_CallContext) ? - static_cast<QV4::Heap::CallContext *>(ctx) : nullptr; + if (!ctx || (ctx->type != QV4::Heap::ExecutionContext::Type_CallContext && ctx->type != QV4::Heap::ExecutionContext::Type_BlockContext)) + return nullptr; + return static_cast<QV4::Heap::CallContext *>(ctx); } QVector<QV4::Heap::ExecutionContext::ContextType> QV4DataCollector::getScopeTypes(int frame) @@ -108,8 +109,9 @@ int QV4DataCollector::encodeScopeType(QV4::Heap::ExecutionContext::ContextType s return 2; case QV4::Heap::ExecutionContext::Type_CallContext: return 1; - case QV4::Heap::ExecutionContext::Type_QmlContext: - default: + case QV4::Heap::ExecutionContext::Type_BlockContext: + return 3; + default: // QV4::Heap::ExecutionContext::Type_QmlContext: return -1; } } diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index badf89fe44..c4b5859b58 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -2199,7 +2199,7 @@ QV4::Compiler::Codegen::Reference JSCodeGen::fallbackNameLookup(const QString &n // Look for IDs first. for (const IdMapping &mapping : qAsConst(_idObjects)) { if (name == mapping.name) { - if (_context->type == QV4::Compiler::ContextType::Binding) + if (_context->contextType == QV4::Compiler::ContextType::Binding) _context->idObjectDependencies.insert(mapping.idIndex); Instruction::LoadIdObject load; diff --git a/src/qml/compiler/qv4bytecodegenerator_p.h b/src/qml/compiler/qv4bytecodegenerator_p.h index 9bd9d82e0d..286ceed57d 100644 --- a/src/qml/compiler/qv4bytecodegenerator_p.h +++ b/src/qml/compiler/qv4bytecodegenerator_p.h @@ -133,14 +133,16 @@ public: }; struct ExceptionHandler : public Label { + ExceptionHandler() = default; ExceptionHandler(BytecodeGenerator *generator) : Label(generator, LinkLater) { } ~ExceptionHandler() { - Q_ASSERT(generator->currentExceptionHandler != this); + Q_ASSERT(!generator || generator->currentExceptionHandler != this); } + bool isValid() const { return generator != nullptr; } }; Label label() { @@ -281,7 +283,7 @@ private: QVector<I> instructions; QVector<int> labels; - ExceptionHandler *currentExceptionHandler; + ExceptionHandler *currentExceptionHandler = nullptr; int regCount = 0; public: int currentReg = 0; diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index d71cd0cdb3..463a1e8d27 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -137,7 +137,6 @@ void Codegen::enterContext(Node *node) int Codegen::leaveContext() { Q_ASSERT(_context); - Q_ASSERT(!_context->controlFlow); int functionIndex = _context->functionIndex; _context = _context->parent; return functionIndex; @@ -2240,7 +2239,7 @@ bool Codegen::visit(FunctionDeclaration * ast) RegisterScope scope(this); - if (_context->type == ContextType::Binding) + if (_functionContext->contextType == ContextType::Binding) referenceForName(ast->name.toString(), true).loadInAccumulator(); _expr.accept(nx); return false; @@ -2252,7 +2251,7 @@ bool Codegen::visit(YieldExpression *ast) return false; } -static bool endsWithReturn(Node *node) +static bool endsWithReturn(Module *module, Node *node) { if (!node) return false; @@ -2261,16 +2260,22 @@ static bool endsWithReturn(Node *node) if (AST::cast<ThrowStatement *>(node)) return true; if (Program *p = AST::cast<Program *>(node)) - return endsWithReturn(p->statements); + return endsWithReturn(module, p->statements); if (StatementList *sl = AST::cast<StatementList *>(node)) { while (sl->next) sl = sl->next; - return endsWithReturn(sl->statement); + 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 (Block *b = AST::cast<Block *>(node)) - return endsWithReturn(b->statements); if (IfStatement *is = AST::cast<IfStatement *>(node)) - return is->ko && endsWithReturn(is->ok) && endsWithReturn(is->ko); + return is->ko && endsWithReturn(module, is->ok) && endsWithReturn(module, is->ko); return false; } @@ -2292,7 +2297,12 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, _module->functions.append(_context); _context->functionIndex = _module->functions.count() - 1; - _context->hasDirectEval |= (_context->type == ContextType::Eval || _context->type == ContextType::Global || _module->debugMode); // Conditional breakpoints are like eval in the function + Context *savedFunctionContext = _functionContext; + _functionContext = _context; + ControlFlow *savedControlFlow = controlFlow; + controlFlow = nullptr; + + _context->hasDirectEval |= (_context->contextType == ContextType::Eval || _context->contextType == ContextType::Global || _module->debugMode); // Conditional breakpoints are like eval in the function // When a user writes the following QML signal binding: // onSignal: function() { doSomethingUsefull } @@ -2319,24 +2329,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, qSwap(_returnAddress, returnAddress); RegisterScope registerScope(this); - _context->emitHeaderBytecode(this); - - for (const Context::Member &member : qAsConst(_context->members)) { - if (member.function) { - const int function = defineFunction(member.function->name.toString(), member.function, member.function->formals, - member.function->body); - loadClosure(function); - if (! _context->parent) { - Reference::fromName(this, member.function->name.toString()).storeConsumeAccumulator(); - } else { - int idx = member.index; - Q_ASSERT(idx >= 0); - Reference local = member.canEscape ? Reference::fromScopedLocal(this, idx, 0) - : Reference::fromStackSlot(this, idx, true); - local.storeConsumeAccumulator(); - } - } - } + _context->emitBlockHeader(this); int argc = 0; while (formals) { @@ -2367,7 +2360,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, statementList(body); - if (hasError || !endsWithReturn(body)) { + if (hasError || !endsWithReturn(_module, body)) { bytecodeGenerator->setLocation(ast->lastSourceLocation()); if (requiresReturnValue) { if (_returnAddress >= 0) { @@ -2381,6 +2374,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, bytecodeGenerator->addInstruction(Instruction::Ret()); } + Q_ASSERT(_context == _functionContext); bytecodeGenerator->finalize(_context); _context->registerCountInFunction = bytecodeGenerator->registerCount(); static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); @@ -2395,6 +2389,8 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, qSwap(_returnAddress, returnAddress); qSwap(requiresReturnValue, _requiresReturnValue); bytecodeGenerator = savedBytecodeGenerator; + controlFlow = savedControlFlow; + _functionContext = savedFunctionContext; return leaveContext(); } @@ -2406,7 +2402,14 @@ bool Codegen::visit(Block *ast) RegisterScope scope(this); - statementList(ast->statements); + enterContext(ast); + _module->blocks.append(_context); + _context->blockIndex = _module->blocks.count() - 1; + { + ControlFlowBlock controlFlow(this, _context); + statementList(ast->statements); + } + leaveContext(); return false; } @@ -2415,12 +2418,12 @@ bool Codegen::visit(BreakStatement *ast) if (hasError) return false; - if (!_context->controlFlow) { + if (!controlFlow) { throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Break outside of loop")); return false; } - ControlFlow::Handler h = _context->controlFlow->getHandler(ControlFlow::Break, ast->label.toString()); + ControlFlow::Handler h = controlFlow->getHandler(ControlFlow::Break, ast->label.toString()); if (h.type == ControlFlow::Invalid) { if (ast->label.isEmpty()) throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Break outside of loop")); @@ -2429,7 +2432,7 @@ bool Codegen::visit(BreakStatement *ast) return false; } - _context->controlFlow->jumpToHandler(h); + controlFlow->jumpToHandler(h); return false; } @@ -2441,12 +2444,12 @@ bool Codegen::visit(ContinueStatement *ast) RegisterScope scope(this); - if (!_context->controlFlow) { + if (!controlFlow) { throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Continue outside of loop")); return false; } - ControlFlow::Handler h = _context->controlFlow->getHandler(ControlFlow::Continue, ast->label.toString()); + ControlFlow::Handler h = controlFlow->getHandler(ControlFlow::Continue, ast->label.toString()); if (h.type == ControlFlow::Invalid) { if (ast->label.isEmpty()) throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString())); @@ -2455,7 +2458,7 @@ bool Codegen::visit(ContinueStatement *ast) return false; } - _context->controlFlow->jumpToHandler(h); + controlFlow->jumpToHandler(h); return false; } @@ -2617,7 +2620,7 @@ bool Codegen::visit(IfStatement *ast) trueLabel.link(); statement(ast->ok); if (ast->ko) { - if (endsWithReturn(ast)) { + if (endsWithReturn(_module, ast)) { falseLabel.link(); statement(ast->ko); } else { @@ -2641,7 +2644,7 @@ bool Codegen::visit(LabelledStatement *ast) RegisterScope scope(this); // check that no outer loop contains the label - ControlFlow *l = _context->controlFlow; + ControlFlow *l = controlFlow; while (l) { if (l->label() == ast->label) { QString error = QString(QStringLiteral("Label '%1' has already been declared")).arg(ast->label.toString()); @@ -2756,7 +2759,7 @@ bool Codegen::visit(ReturnStatement *ast) if (hasError) return true; - if (_context->type != ContextType::Function && _context->type != ContextType::Binding) { + if (_functionContext->contextType != ContextType::Function && _functionContext->contextType != ContextType::Binding) { throwSyntaxError(ast->returnToken, QStringLiteral("Return statement outside of function")); return false; } @@ -2769,13 +2772,13 @@ bool Codegen::visit(ReturnStatement *ast) expr = Reference::fromConst(this, Encode::undefined()); } - if (_context->controlFlow && _context->controlFlow->returnRequiresUnwind()) { + if (controlFlow && controlFlow->returnRequiresUnwind()) { if (_returnAddress >= 0) (void) expr.storeOnStack(_returnAddress); else expr.loadInAccumulator(); - ControlFlow::Handler h = _context->controlFlow->getHandler(ControlFlow::Return); - _context->controlFlow->jumpToHandler(h); + ControlFlow::Handler h = controlFlow->getHandler(ControlFlow::Return); + controlFlow->jumpToHandler(h); } else { expr.loadInAccumulator(); bytecodeGenerator->addInstruction(Instruction::Ret()); @@ -2872,8 +2875,8 @@ bool Codegen::visit(ThrowStatement *ast) if (hasError) return false; - if (_context->controlFlow) { - _context->controlFlow->handleThrow(expr); + if (controlFlow) { + controlFlow->handleThrow(expr); } else { expr.loadInAccumulator(); Instruction::ThrowException instr; diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 10926dd30b..3c64e0f00e 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -493,12 +493,12 @@ public: int registerSetterLookup(int nameIndex) { return jsUnitGenerator->registerSetterLookup(nameIndex); } int registerGlobalGetterLookup(int nameIndex) { return jsUnitGenerator->registerGlobalGetterLookup(nameIndex); } -protected: // Returns index in _module->functions 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, const BytecodeGenerator::Label *iftrue, @@ -517,8 +517,6 @@ protected: void destructurePropertyList(const Reference &object, AST::PatternPropertyList *bindingList); void destructureElementList(const Reference &array, AST::PatternElementList *bindingList); - void loadClosure(int index); - // Hook provided to implement QML lookup semantics virtual Reference fallbackNameLookup(const QString &name); virtual void beginFunctionBodyHook() {} @@ -653,6 +651,8 @@ public: Context *currentContext() const { return _context; } BytecodeGenerator *generator() const { return bytecodeGenerator; } + void loadClosure(int index); + protected: friend class ScanFunctions; friend struct ControlFlow; @@ -663,12 +663,14 @@ protected: Module *_module; int _returnAddress; Context *_context; + Context *_functionContext = nullptr; AST::LabelledStatement *_labelledStatement; QV4::Compiler::JSUnitGenerator *jsUnitGenerator; BytecodeGenerator *bytecodeGenerator = nullptr; bool _strictMode; bool useFastLookups = true; bool requiresReturnValue = false; + ControlFlow *controlFlow = nullptr; bool _fileNameIsUrl; bool hasError; diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 59c37fb6c7..636b74e0fc 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -260,6 +260,9 @@ void CompilationUnit::markObjects(QV4::MarkStack *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); if (runtimeLookups) { for (uint i = 0; i < data->lookupTableSize; ++i) @@ -383,6 +386,21 @@ void CompilationUnit::linkBackendToEngine(ExecutionEngine *engine) const QV4::CompiledData::Function *compiledFunction = data->functionAt(i); runtimeFunctions[i] = new QV4::Function(engine, this, compiledFunction, &Moth::VME::exec); } + + 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); + + // first locals + const quint32_le *localsIndices = compiledBlock->localsTable(); + for (quint32 i = 0; i < compiledBlock->nLocals; ++i) + ic = ic->addMember(engine->identifierTable->identifier(runtimeStrings[localsIndices[i]]), Attr_NotConfigurable); + runtimeBlocks[i] = ic->d(); + } } #endif // V4_BOOTSTRAP diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index 8f49fbd728..156fbc0149 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -202,6 +202,25 @@ struct CodeOffsetToLine { quint32_le line; }; +struct Block +{ + quint32_le nLocals; + quint32_le localsOffset; + + 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); + } +}; + // Function is aligned on an 8-byte boundary to make sure there are no bus errors or penalties // for unaligned access. The ordering of the fields is also from largest to smallest. struct Function @@ -240,16 +259,14 @@ struct Function quint32_le dependingScopePropertiesOffset; // Array of int pairs (property index and notify index) // Qml Extensions End -// quint32 formalsIndex[nFormals] -// quint32 localsIndex[nLocals] -// quint32 offsetForInnerFunctions[nInnerFunctions] -// Function[nInnerFunctions] - // Keep all unaligned data at the end quint8 flags; quint8 padding1; quint16_le padding2; + // quint32 formalsIndex[nFormals] + // quint32 localsIndex[nLocals] + 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); } @@ -710,6 +727,8 @@ struct Unit quint32_le offsetToStringTable; quint32_le functionTableSize; quint32_le offsetToFunctionTable; + quint32_le blockTableSize; + quint32_le offsetToBlockTable; quint32_le lookupTableSize; quint32_le offsetToLookupTable; quint32_le regexpTableSize; @@ -771,6 +790,7 @@ struct Unit } const quint32_le *functionOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToFunctionTable); } + const quint32_le *blockOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToBlockTable); } const Function *functionAt(int idx) const { const quint32_le *offsetTable = functionOffsetTable(); @@ -778,6 +798,12 @@ struct Unit return reinterpret_cast<const Function*>(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)); @@ -796,7 +822,7 @@ struct Unit } }; -static_assert(sizeof(Unit) == 192, "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) == 200, "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 { @@ -945,6 +971,7 @@ public: QV4::Lookup *runtimeLookups = nullptr; QVector<QV4::Function *> runtimeFunctions; + QVector<QV4::Heap::InternalClass *> runtimeBlocks; mutable QQmlNullableValue<QUrl> m_url; mutable QQmlNullableValue<QUrl> m_finalUrl; diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index d9f37b542f..d5b77f61cb 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -233,28 +233,39 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO for (int i = 0; i < f->locals.size(); ++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)); + } - Q_ALLOCA_VAR(quint32_le, functionOffsets, module->functions.size() * sizeof(quint32_le)); + Q_ALLOCA_VAR(quint32_le, blockAndFunctionOffsets, (module->functions.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, blockAndFunctionOffsets, &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(quint32_le)); + memcpy(dataPtr + unit->offsetToFunctionTable, blockAndFunctionOffsets, unit->functionTableSize * sizeof(quint32_le)); + memcpy(dataPtr + unit->offsetToBlockTable, blockAndFunctionOffsets + unit->functionTableSize, unit->blockTableSize * sizeof(quint32_le)); 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 + blockAndFunctionOffsets[i], function); + } + + for (int i = 0; i < module->blocks.size(); ++i) { + Context *block = module->blocks.at(i); + + writeBlock(dataPtr + blockAndFunctionOffsets[i + module->functions.size()], block); } CompiledData::Lookup *lookupsToWrite = reinterpret_cast<CompiledData::Lookup*>(dataPtr + unit->offsetToLookupTable); @@ -395,7 +406,24 @@ void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::Compiler::Conte memcpy(f + function->codeOffset, irFunction->code.constData(), irFunction->code.size()); } -QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Compiler::JSUnitGenerator::GeneratorOption option, quint32_le *functionOffsets, uint *jsClassDataOffset) +void QV4::Compiler::JSUnitGenerator::writeBlock(char *b, QV4::Compiler::Context *irBlock) const +{ + QV4::CompiledData::Block *block = reinterpret_cast<QV4::CompiledData::Block *>(b); + + quint32 currentOffset = sizeof(QV4::CompiledData::Block); + currentOffset = (currentOffset + 7) & ~quint32(0x7); + + 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)); +} + +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)); @@ -414,6 +442,10 @@ QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Comp unit.offsetToFunctionTable = nextOffset; nextOffset += unit.functionTableSize * 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); @@ -440,13 +472,21 @@ QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Comp for (int i = 0; i < module->functions.size(); ++i) { Context *f = module->functions.at(i); - functionOffsets[i] = nextOffset; + blockAndFunctionOffsets[i] = nextOffset; const int qmlIdDepsCount = f->idObjectDependencies.count(); const int qmlPropertyDepsCount = f->scopeObjectPropertyDependencies.count() + f->contextObjectPropertyDependencies.count(); nextOffset += QV4::CompiledData::Function::calculateSize(f->arguments.size(), f->locals.size(), f->lineNumberMapping.size(), f->nestedContexts.size(), qmlIdDepsCount, qmlPropertyDepsCount, f->code.size()); } + blockAndFunctionOffsets += module->functions.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(); diff --git a/src/qml/compiler/qv4compiler_p.h b/src/qml/compiler/qv4compiler_p.h index 360af6540f..bd330d3353 100644 --- a/src/qml/compiler/qv4compiler_p.h +++ b/src/qml/compiler/qv4compiler_p.h @@ -125,8 +125,8 @@ struct Q_QML_PRIVATE_EXPORT JSUnitGenerator { }; QV4::CompiledData::Unit *generateUnit(GeneratorOption option = GenerateWithStringTable); - // Returns bytes written void writeFunction(char *f, Context *irFunction) const; + void writeBlock(char *f, Context *irBlock) const; StringTableGenerator stringTable; QString codeGeneratorName; diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index e2e978be7c..4893a06af8 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -71,19 +71,12 @@ Context *Module::newContext(Node *node, Context *parent, ContextType contextType return c; } -bool Context::forceLookupByName() -{ - ControlFlow *flow = controlFlow; - while (flow) { - if (flow->needsLookupByName) - return true; - flow = flow->parent; - } - return false; -} - bool Context::addLocalVar(const QString &name, Context::MemberType type, VariableScope scope, FunctionExpression *function) { + // hoist var declarations to the function level + if (contextType == ContextType::Block && (scope == VariableScope::Var && type != MemberType::FunctionDefinition)) + return parent->addLocalVar(name, type, scope, function); + if (name.isEmpty()) return true; @@ -117,7 +110,7 @@ Context::ResolvedName Context::resolveName(const QString &name) ResolvedName result; while (c->parent) { - if (c->forceLookupByName()) + if (c->forceLookupByName) return result; Context::Member m = c->findMember(name); @@ -128,7 +121,6 @@ Context::ResolvedName Context::resolveName(const QString &name) if (c->isStrict && (name == QLatin1String("arguments") || name == QLatin1String("eval"))) result.isArgOrEval = true; return result; - Q_ASSERT(result.type != ResolvedName::Stack || result.scope == 0); } const int argIdx = c->findArgument(name); if (argIdx != -1) { @@ -145,7 +137,7 @@ Context::ResolvedName Context::resolveName(const QString &name) return result; } } - if (!c->isStrict && c->hasDirectEval) + if (!c->isStrict && c->hasDirectEval && c->contextType != ContextType::Block) return result; if (c->requiresExecutionContext) @@ -154,25 +146,32 @@ Context::ResolvedName Context::resolveName(const QString &name) } // ### can we relax the restrictions here? - if (c->forceLookupByName() || type == ContextType::Eval || c->type == ContextType::Binding) + if (c->forceLookupByName || contextType == ContextType::Eval || c->contextType == ContextType::Binding) return result; result.type = ResolvedName::Global; return result; } -void Context::emitHeaderBytecode(Codegen *codegen) +int Context::emitBlockHeader(Codegen *codegen) { - using Instruction = Moth::Instruction; Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator(); setupFunctionIndices(bytecodeGenerator); + int contextReg = -1; if (requiresExecutionContext || - type == ContextType::Binding) { // we don't really need this for bindings, but we do for signal handlers, and we don't know if the code is a signal handler or not. - Instruction::CreateCallContext createContext; - bytecodeGenerator->addInstruction(createContext); + contextType == ContextType::Binding) { // we don't really need this for bindings, but we do for signal handlers, and we don't know if the code is a signal handler or not. + if (contextType == ContextType::Block) { + Instruction::PushBlockContext blockContext; + blockContext.index = blockIndex; + blockContext.reg = contextReg = bytecodeGenerator->newRegister(); + bytecodeGenerator->addInstruction(blockContext); + } else { + Instruction::CreateCallContext createContext; + bytecodeGenerator->addInstruction(createContext); + } } if (usesThis && !isStrict) { // make sure we convert this to an object @@ -180,8 +179,10 @@ void Context::emitHeaderBytecode(Codegen *codegen) bytecodeGenerator->addInstruction(convert); } - // variables in global code are properties of the global context object, not locals as with other functions. - if (type == ContextType::Function || type == ContextType::Binding) { + switch (contextType) { + 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 && it->type == Context::ThisFunctionName) { // move the function from the stack to the call context @@ -193,7 +194,11 @@ void Context::emitHeaderBytecode(Codegen *codegen) bytecodeGenerator->addInstruction(store); } } - } else { + break; + } + case ContextType::Global: + case ContextType::Eval: { + // 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) { const QString &local = it.key(); @@ -203,8 +208,10 @@ void Context::emitHeaderBytecode(Codegen *codegen) bytecodeGenerator->addInstruction(declareVar); } } + } if (usesArgumentsObject == Context::ArgumentsObjectUsed) { + Q_ASSERT(contextType != ContextType::Block); if (isStrict || (formals && !formals->isSimpleParameterList())) { Instruction::CreateUnmappedArgumentsObject setup; bytecodeGenerator->addInstruction(setup); @@ -214,6 +221,38 @@ void Context::emitHeaderBytecode(Codegen *codegen) } 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); + if (!parent) { + Codegen::Reference::fromName(codegen, member.function->name.toString()).storeConsumeAccumulator(); + } else { + int idx = member.index; + Q_ASSERT(idx >= 0); + Codegen::Reference local = member.canEscape ? Codegen::Reference::fromScopedLocal(codegen, idx, 0) + : Codegen::Reference::fromStackSlot(codegen, idx, true); + local.storeConsumeAccumulator(); + } + } + } + + + + return contextReg; +} + +void Context::emitBlockFooter(Codegen *codegen, int oldContextReg) +{ + using Instruction = Moth::Instruction; + Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator(); + + if (oldContextReg != -1) { + Instruction::PopContext popContext; + popContext.reg = oldContextReg; + bytecodeGenerator->addInstruction(popContext); + } } void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator) @@ -222,7 +261,10 @@ void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator) Q_ASSERT(nRegisters == 0); registerOffset = bytecodeGenerator->registerCount(); - if (type == ContextType::Function || type == ContextType::Binding) { + switch (contextType) { + case ContextType::Block: + case ContextType::Function: + case ContextType::Binding: { for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) { const QString &local = it.key(); if (it->canEscape) { @@ -235,6 +277,11 @@ void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator) it->index = bytecodeGenerator->newRegister(); } } + break; + } + case ContextType::Global: + case ContextType::Eval: + break; } nRegisters = bytecodeGenerator->registerCount() - registerOffset; } diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h index 289488cbc8..189a623703 100644 --- a/src/qml/compiler/qv4compilercontext_p.h +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -91,6 +91,7 @@ struct Module { QHash<QQmlJS::AST::Node *, Context *> contextMap; QList<Context *> functions; + QList<Context *> blocks; Context *rootContext; QString fileName; QString finalUrl; @@ -107,6 +108,7 @@ struct Context { int column = 0; int registerCountInFunction = 0; int functionIndex = -1; + int blockIndex = -1; enum MemberType { UndefinedMember, @@ -152,6 +154,7 @@ struct Context { bool returnsClosure = false; mutable bool argumentsCanEscape = false; bool requiresExecutionContext = false; + bool forceLookupByName = false; enum UsesArgumentsObject { ArgumentsObjectUnknown, @@ -161,7 +164,7 @@ struct Context { UsesArgumentsObject usesArgumentsObject = ArgumentsObjectUnknown; - ContextType type; + ContextType contextType; template <typename T> class SmallSet: public QVarLengthArray<T, 8> @@ -212,14 +215,12 @@ struct Context { Context(Context *parent, ContextType type) : parent(parent) - , type(type) + , contextType(type) { if (parent && parent->isStrict) isStrict = true; } - bool forceLookupByName(); - int findArgument(const QString &name) { // search backwards to handle duplicate argument names correctly @@ -252,16 +253,16 @@ struct Context { } bool requiresImplicitReturnValue() const { - return type == ContextType::Binding || - type == ContextType::Eval || - type == ContextType::Global; + return contextType == ContextType::Binding || + contextType == ContextType::Eval || + contextType == ContextType::Global; } void addUsedVariable(const QString &name) { usedVariables.insert(name); } - bool addLocalVar(const QString &name, MemberType type, QQmlJS::AST::VariableScope scope, QQmlJS::AST::FunctionExpression *function = nullptr); + bool addLocalVar(const QString &name, MemberType contextType, QQmlJS::AST::VariableScope scope, QQmlJS::AST::FunctionExpression *function = nullptr); struct ResolvedName { enum Type { @@ -277,7 +278,9 @@ struct Context { bool isValid() const { return type != Unresolved; } }; ResolvedName resolveName(const QString &name); - void emitHeaderBytecode(Compiler::Codegen *codegen); + int emitBlockHeader(Compiler::Codegen *codegen); + void emitBlockFooter(Compiler::Codegen *codegen, int oldContextReg); + void setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator); }; diff --git a/src/qml/compiler/qv4compilercontrolflow_p.h b/src/qml/compiler/qv4compilercontrolflow_p.h index 9bda20905a..c02168c1b6 100644 --- a/src/qml/compiler/qv4compilercontrolflow_p.h +++ b/src/qml/compiler/qv4compilercontrolflow_p.h @@ -68,6 +68,7 @@ struct ControlFlow { enum Type { Loop, With, + Block, Finally, Catch }; @@ -91,16 +92,18 @@ struct ControlFlow { Codegen *cg; ControlFlow *parent; Type type; - bool needsLookupByName = false; + bool contextUsedLookupByName; ControlFlow(Codegen *cg, Type type) - : cg(cg), parent(cg->_context->controlFlow), type(type) + : cg(cg), parent(cg->controlFlow), type(type) { - cg->_context->controlFlow = this; + contextUsedLookupByName = cg->_context->forceLookupByName; + cg->controlFlow = this; } virtual ~ControlFlow() { - cg->_context->controlFlow = parent; + cg->controlFlow = parent; + cg->_context->forceLookupByName = contextUsedLookupByName; } void emitReturnStatement() const { @@ -235,9 +238,14 @@ struct ControlFlowUnwind : public ControlFlow QVector<Handler> handlers; ControlFlowUnwind(Codegen *cg, Type type) - : ControlFlow(cg, type), unwindLabel(generator()->newExceptionHandler()) + : ControlFlow(cg, type) { Q_ASSERT(type != Loop); + } + + void setupExceptionHandler() + { + unwindLabel = generator()->newExceptionHandler(); controlFlowTemp = static_cast<int>(generator()->newRegister()); Reference::storeConstOnStack(cg, QV4::Encode::undefined(), controlFlowTemp); // we'll need at least a handler for throw @@ -289,7 +297,7 @@ struct ControlFlowUnwind : public ControlFlow } virtual BytecodeGenerator::ExceptionHandler *exceptionHandler() { - return &unwindLabel; + return unwindLabel.isValid() ? &unwindLabel : parentExceptionHandler(); } virtual void emitForThrowHandling() { } @@ -300,8 +308,9 @@ struct ControlFlowWith : public ControlFlowUnwind ControlFlowWith(Codegen *cg) : ControlFlowUnwind(cg, With) { - needsLookupByName = true; + cg->currentContext()->forceLookupByName = true; + setupExceptionHandler(); savedContextRegister = Moth::StackSlot::createRegister(generator()->newRegister()); // assumes the with object is in the accumulator @@ -325,18 +334,56 @@ struct ControlFlowWith : public ControlFlowUnwind Moth::StackSlot savedContextRegister; }; +struct ControlFlowBlock : public ControlFlowUnwind +{ + ControlFlowBlock(Codegen *cg, Context *block) + : ControlFlowUnwind(cg, Block), + block(block) + { + savedContextRegister = block->emitBlockHeader(cg); + + if (savedContextRegister != -1) { + setupExceptionHandler(); + generator()->setExceptionHandler(&unwindLabel); + } + } + + virtual ~ControlFlowBlock() { + // emit code for unwinding + if (savedContextRegister != -1) { + unwindLabel.link(); + generator()->setExceptionHandler(parentExceptionHandler()); + } + + block->emitBlockFooter(cg, savedContextRegister); + + if (savedContextRegister != -1) + emitUnwindHandler(); + } + virtual Handler getHandler(HandlerType type, const QString &label = QString()) { + if (savedContextRegister == -1) + return getParentHandler(type, label); + return ControlFlowUnwind::getHandler(type, label); + } + + int savedContextRegister = -1; + Context *block; +}; + struct ControlFlowCatch : public ControlFlowUnwind { AST::Catch *catchExpression; bool insideCatch = false; BytecodeGenerator::ExceptionHandler exceptionLabel; BytecodeGenerator::ExceptionHandler catchUnwindLabel; + bool oldLookupByName; ControlFlowCatch(Codegen *cg, AST::Catch *catchExpression) : ControlFlowUnwind(cg, Catch), catchExpression(catchExpression), exceptionLabel(generator()->newExceptionHandler()), catchUnwindLabel(generator()->newExceptionHandler()) { + setupExceptionHandler(); generator()->setExceptionHandler(&exceptionLabel); } @@ -363,7 +410,7 @@ struct ControlFlowCatch : public ControlFlowUnwind ~ControlFlowCatch() { // emit code for unwinding - needsLookupByName = true; + cg->_context->forceLookupByName = true; insideCatch = true; Codegen::RegisterScope scope(cg); @@ -382,7 +429,6 @@ struct ControlFlowCatch : public ControlFlowUnwind cg->statement(catchExpression->statement); insideCatch = false; - needsLookupByName = false; // exceptions inside catch and break/return statements go here catchUnwindLabel.link(); @@ -408,6 +454,7 @@ struct ControlFlowFinally : public ControlFlowUnwind : ControlFlowUnwind(cg, Finally), finally(finally) { Q_ASSERT(finally != nullptr); + setupExceptionHandler(); generator()->setExceptionHandler(&unwindLabel); } diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index 4fa35f3adb..312f07e254 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -395,10 +395,17 @@ bool ScanFunctions::visit(ThisExpression *) bool ScanFunctions::visit(Block *ast) { TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _context->isStrict ? false : _allowFuncDecls); + enterEnvironment(ast, ContextType::Block); + _context->name = QLatin1String("Block"); Node::accept(ast->statements, this); return false; } +void ScanFunctions::endVisit(Block *) +{ + leaveEnvironment(); +} + bool ScanFunctions::enterFunction(Node *ast, const QString &name, FormalParameterList *formals, StatementList *body, bool enterName) { Context *outerContext = _context; @@ -421,6 +428,7 @@ bool ScanFunctions::enterFunction(Node *ast, const QString &name, FormalParamete outerContext->usesArgumentsObject = Context::ArgumentsObjectNotUsed; } + _context->name = name; if (formals && formals->containsName(QStringLiteral("arguments"))) _context->usesArgumentsObject = Context::ArgumentsObjectNotUsed; if (expr) { @@ -496,18 +504,29 @@ void ScanFunctions::calcEscapingVariables() c->hasWith |= inner->hasWith; c = c->parent; } + if (inner->contextType == ContextType::Block && inner->usesArgumentsObject == Context::ArgumentsObjectUsed) { + Context *f = inner->parent; + while (f->contextType == ContextType::Block) + f = f->parent; + f->usesArgumentsObject = Context::ArgumentsObjectUsed; + inner->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + } } for (Context *c : qAsConst(m->contextMap)) { - bool allVarsEscape = c->hasWith || c->hasTry || c->hasDirectEval || m->debugMode; + bool allVarsEscape = c->hasWith || c->hasTry || c->hasDirectEval; + if (allVarsEscape && c->contextType == ContextType::Block && c->members.isEmpty()) + allVarsEscape = false; + if (m->debugMode) + allVarsEscape = true; if (allVarsEscape) { c->requiresExecutionContext = true; c->argumentsCanEscape = true; } // ### for now until we have lexically scoped vars that'll require it - if (c->type == ContextType::Global) + if (c->contextType == ContextType::Global) c->requiresExecutionContext = false; // ### Shouldn't be required, we could probably rather change the ContextType to FunctionCode for strict eval - if (c->type == ContextType::Eval && c->isStrict) + if (c->contextType == ContextType::Eval && c->isStrict) c->requiresExecutionContext = true; if (!c->parent || c->usesArgumentsObject == Context::ArgumentsObjectUnknown) c->usesArgumentsObject = Context::ArgumentsObjectNotUsed; @@ -529,11 +548,12 @@ void ScanFunctions::calcEscapingVariables() if (showEscapingVars) { qDebug() << "==== escaping variables ===="; for (Context *c : qAsConst(m->contextMap)) { - qDebug() << "Context" << c << c->name << ":"; + qDebug() << "Context" << c << c->name << "requiresExecutionContext" << c->requiresExecutionContext; + 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().canEscape; + qDebug() << " " << it.key() << it.value().canEscape << "isLexicallyScoped:" << it.value().isLexicallyScoped(); } } } diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index fc24481ad4..d6969c56e8 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -134,6 +134,7 @@ protected: bool visit(AST::ThisExpression *ast) override; bool visit(AST::Block *ast) override; + void endVisit(AST::Block *ast) override; protected: bool enterFunction(AST::Node *ast, const QString &name, AST::FormalParameterList *formals, AST::StatementList *body, bool enterName); diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index c50e9c8379..094611e974 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -404,6 +404,10 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st d << dumpRegister(reg, nFormals); MOTH_END_INSTR(PushWithContext) + MOTH_BEGIN_INSTR(PushBlockContext) + d << dumpRegister(reg, nFormals) << ", " << index; + MOTH_END_INSTR(PushBlockContext) + MOTH_BEGIN_INSTR(PopContext) d << dumpRegister(reg, nFormals); MOTH_END_INSTR(PopContext) diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index 763e0adce1..2e1763d3be 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -114,6 +114,7 @@ QT_BEGIN_NAMESPACE #define INSTR_CreateCallContext(op) INSTRUCTION(op, CreateCallContext, 0) #define INSTR_PushCatchContext(op) INSTRUCTION(op, PushCatchContext, 2, name, reg) #define INSTR_PushWithContext(op) INSTRUCTION(op, PushWithContext, 1, reg) +#define INSTR_PushBlockContext(op) INSTRUCTION(op, PushBlockContext, 2, reg, index) #define INSTR_PopContext(op) INSTRUCTION(op, PopContext, 1, reg) #define INSTR_ForeachIteratorObject(op) INSTRUCTION(op, ForeachIteratorObject, 0) #define INSTR_ForeachNextPropertyName(op) INSTRUCTION(op, ForeachNextPropertyName, 0) @@ -235,6 +236,7 @@ QT_BEGIN_NAMESPACE F(CreateCallContext) \ F(PushCatchContext) \ F(PushWithContext) \ + F(PushBlockContext) \ F(PopContext) \ F(ForeachIteratorObject) \ F(ForeachNextPropertyName) \ diff --git a/src/qml/jit/qv4jit.cpp b/src/qml/jit/qv4jit.cpp index aa9c244a60..4a2e2b959c 100644 --- a/src/qml/jit/qv4jit.cpp +++ b/src/qml/jit/qv4jit.cpp @@ -629,6 +629,24 @@ void BaselineJIT::generate_PushWithContext(int reg) as->checkException(); } +static void pushBlockContextHelper(QV4::Value *stack, int reg, int index) +{ + stack[reg] = stack[CallData::Context]; + ExecutionContext *c = static_cast<ExecutionContext *>(stack + CallData::Context); + stack[CallData::Context] = Runtime::method_createBlockContext(c, index); +} + +void BaselineJIT::generate_PushBlockContext(int reg, int index) +{ + as->saveAccumulatorInFrame(); + as->prepareCallWithArgCount(3); + as->passInt32AsArg(index, 2); + as->passInt32AsArg(reg, 1); + as->passRegAsArg(0, 0); + JIT_GENERATE_RUNTIME_CALL(pushBlockContextHelper, Assembler::IgnoreResult); +} + + void BaselineJIT::generate_PopContext(int reg) { as->popContext(reg); } void BaselineJIT::generate_ForeachIteratorObject() @@ -1177,6 +1195,9 @@ void BaselineJIT::collectLabelsInBytecode() MOTH_BEGIN_INSTR(PushWithContext) MOTH_END_INSTR(PushWithContext) + MOTH_BEGIN_INSTR(PushBlockContext) + MOTH_END_INSTR(PushBlockContext) + MOTH_BEGIN_INSTR(PopContext) MOTH_END_INSTR(PopContext) diff --git a/src/qml/jit/qv4jit_p.h b/src/qml/jit/qv4jit_p.h index 7861cdec38..3e81807754 100644 --- a/src/qml/jit/qv4jit_p.h +++ b/src/qml/jit/qv4jit_p.h @@ -177,6 +177,7 @@ public: void generate_CreateCallContext() override; void generate_PushCatchContext(int name, int reg) override; void generate_PushWithContext(int reg) override; + void generate_PushBlockContext(int reg, int index) override; void generate_PopContext(int reg) override; void generate_ForeachIteratorObject() override; void generate_ForeachNextPropertyName() override; diff --git a/src/qml/jsruntime/qv4context.cpp b/src/qml/jsruntime/qv4context.cpp index 020e519e74..39fdcf0227 100644 --- a/src/qml/jsruntime/qv4context.cpp +++ b/src/qml/jsruntime/qv4context.cpp @@ -55,6 +55,29 @@ DEFINE_MANAGED_VTABLE(ExecutionContext); DEFINE_MANAGED_VTABLE(CallContext); DEFINE_MANAGED_VTABLE(CatchContext); +Heap::CallContext *ExecutionContext::newBlockContext(CppStackFrame *frame, int blockIndex) +{ + Function *function = frame->v4Function; + Heap::ExecutionContext *outer = static_cast<Heap::ExecutionContext *>(frame->context()->m()); + + Heap::InternalClass *ic = function->compilationUnit->runtimeBlocks.at(blockIndex); + int nLocals = ic->size; + size_t requiredMemory = sizeof(CallContext::Data) - sizeof(Value) + sizeof(Value) * nLocals; + + ExecutionEngine *v4 = outer->internalClass->engine; + Heap::CallContext *c = v4->memoryManager->allocManaged<CallContext>(requiredMemory, ic); + c->init(); + c->type = Heap::ExecutionContext::Type_BlockContext; + + c->outer.set(v4, outer); + c->function.set(v4, static_cast<Heap::FunctionObject *>(frame->jsFrame->function.m())); + + c->locals.size = nLocals; + c->locals.alloc = nLocals; + + return c; +} + Heap::CallContext *ExecutionContext::newCallContext(CppStackFrame *frame) { Function *function = frame->v4Function; @@ -112,6 +135,7 @@ void ExecutionContext::createMutableBinding(String *name, bool deletable) ScopedContext ctx(scope, this); while (ctx) { switch (ctx->d()->type) { + case Heap::ExecutionContext::Type_BlockContext: case Heap::ExecutionContext::Type_CallContext: if (!activation) { Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx->d()); @@ -170,6 +194,7 @@ bool ExecutionContext::deleteProperty(String *name) return false; break; } + case Heap::ExecutionContext::Type_BlockContext: case Heap::ExecutionContext::Type_CallContext: { Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); uint index = c->internalClass->find(id); @@ -225,6 +250,7 @@ ExecutionContext::Error ExecutionContext::setProperty(String *name, const Value } break; } + case Heap::ExecutionContext::Type_BlockContext: case Heap::ExecutionContext::Type_CallContext: { Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); uint index = c->internalClass->find(id); @@ -273,6 +299,7 @@ ReturnedValue ExecutionContext::getProperty(String *name) return c->exceptionValue.asReturnedValue(); break; } + case Heap::ExecutionContext::Type_BlockContext: case Heap::ExecutionContext::Type_CallContext: { Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); Identifier *id = name->identifier(); @@ -314,6 +341,7 @@ ReturnedValue ExecutionContext::getPropertyAndBase(String *name, Value *base) return c->exceptionValue.asReturnedValue(); break; } + case Heap::ExecutionContext::Type_BlockContext: case Heap::ExecutionContext::Type_CallContext: { Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); name->makeIdentifier(); diff --git a/src/qml/jsruntime/qv4context_p.h b/src/qml/jsruntime/qv4context_p.h index ccc1653c36..5a9c85c941 100644 --- a/src/qml/jsruntime/qv4context_p.h +++ b/src/qml/jsruntime/qv4context_p.h @@ -110,7 +110,8 @@ DECLARE_HEAP_OBJECT(ExecutionContext, Base) { Type_CatchContext = 0x2, Type_WithContext = 0x3, Type_QmlContext = 0x4, - Type_CallContext = 0x5 + Type_BlockContext = 0x5, + Type_CallContext = 0x6 }; void init(ContextType t) @@ -191,6 +192,7 @@ struct Q_QML_EXPORT ExecutionContext : public Managed Q_MANAGED_TYPE(ExecutionContext) V4_INTERNALCLASS(ExecutionContext) + static Heap::CallContext *newBlockContext(QV4::CppStackFrame *frame, int blockIndex); static Heap::CallContext *newCallContext(QV4::CppStackFrame *frame); Heap::ExecutionContext *newWithContext(Heap::Object *with); Heap::CatchContext *newCatchContext(Heap::String *exceptionVarName, ReturnedValue exceptionValue); diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index b14395b507..95e4017344 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -1202,6 +1202,13 @@ ReturnedValue Runtime::method_createCatchContext(ExecutionContext *parent, int e e->catchException(nullptr))->asReturnedValue(); } +ReturnedValue Runtime::method_createBlockContext(ExecutionContext *parent, int index) +{ + ExecutionEngine *e = parent->engine(); + return parent->newBlockContext(e->currentStackFrame, index)->asReturnedValue(); +} + + void Runtime::method_declareVar(ExecutionEngine *engine, bool deletable, int nameIndex) { Scope scope(engine); diff --git a/src/qml/jsruntime/qv4runtimeapi_p.h b/src/qml/jsruntime/qv4runtimeapi_p.h index 9bdb41b19e..24bab70327 100644 --- a/src/qml/jsruntime/qv4runtimeapi_p.h +++ b/src/qml/jsruntime/qv4runtimeapi_p.h @@ -126,6 +126,7 @@ struct ExceptionCheck<void (*)(QV4::NoThrowEngine *, A, B, C)> { F(void, throwException, (ExecutionEngine *engine, const Value &value)) \ F(ReturnedValue, createWithContext, (ExecutionContext *parent, const Value &o)) \ F(ReturnedValue, createCatchContext, (ExecutionContext *parent, int exceptionVarNameIndex)) \ + F(ReturnedValue, createBlockContext, (ExecutionContext *parent, int index)) \ \ /* closures */ \ F(ReturnedValue, closure, (ExecutionEngine *engine, int functionId)) \ diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 8e5b14d0ea..a515abd9de 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -894,6 +894,12 @@ QV4::ReturnedValue VME::exec(const FunctionObject *fo, const QV4::Value *thisObj STACK_VALUE(CallData::Context) = Runtime::method_createWithContext(c, accumulator); MOTH_END_INSTR(PushWithContext) + MOTH_BEGIN_INSTR(PushBlockContext) + STACK_VALUE(reg) = STACK_VALUE(CallData::Context); + ExecutionContext *c = static_cast<ExecutionContext *>(stack + CallData::Context); + STACK_VALUE(CallData::Context) = Runtime::method_createBlockContext(c, index); + MOTH_END_INSTR(PushBlockContext) + MOTH_BEGIN_INSTR(PopContext) STACK_VALUE(CallData::Context) = STACK_VALUE(reg); MOTH_END_INSTR(PopContext) diff --git a/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp index 17a99d6a43..ccb3a2dcdc 100644 --- a/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp +++ b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp @@ -220,7 +220,21 @@ public: { for (int i = 0, ei = m_stackTrace.size(); i != ei; ++i) { m_capturedScope.append(NamedRefs()); - ScopeJob job(&collector, i, 0); + FrameJob frameJob(&collector, i); + debugger->runInEngine(&frameJob); + QJsonObject frameObj = frameJob.returnValue(); + QJsonArray scopes = frameObj.value(QLatin1String("scopes")).toArray(); + int nscopes = scopes.size(); + int s = 0; + for (s = 0; s < nscopes; ++s) { + QJsonObject o = scopes.at(s).toObject(); + if (o.value(QLatin1String("type")).toInt(-2) == 1) // CallContext + break; + } + if (s == nscopes) + return; + + ScopeJob job(&collector, i, s); debugger->runInEngine(&job); NamedRefs &refs = m_capturedScope.last(); QJsonObject object = job.returnValue(); diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations index 55007189b8..9576dca707 100644 --- a/tests/auto/qml/ecmascripttests/TestExpectations +++ b/tests/auto/qml/ecmascripttests/TestExpectations @@ -3938,25 +3938,8 @@ language/arguments-object/mapped/nonconfigurable-nonwritable-descriptors-set-by- language/arguments-object/mapped/nonwritable-nonconfigurable-descriptors-set-by-arguments language/arguments-object/mapped/Symbol.iterator language/arguments-object/unmapped/Symbol.iterator -language/block-scope/leave/finally-block-let-declaration-only-shadows-outer-parameter-value-1 -language/block-scope/leave/finally-block-let-declaration-only-shadows-outer-parameter-value-2 -language/block-scope/leave/for-loop-block-let-declaration-only-shadows-outer-parameter-value-1 -language/block-scope/leave/for-loop-block-let-declaration-only-shadows-outer-parameter-value-2 -language/block-scope/leave/nested-block-let-declaration-only-shadows-outer-parameter-value-1 -language/block-scope/leave/nested-block-let-declaration-only-shadows-outer-parameter-value-2 -language/block-scope/leave/outermost-binding-updated-in-catch-block-nested-block-let-declaration-unseen-outside-of-block -language/block-scope/leave/try-block-let-declaration-only-shadows-outer-parameter-value-1 -language/block-scope/leave/try-block-let-declaration-only-shadows-outer-parameter-value-2 -language/block-scope/leave/verify-context-in-finally-block -language/block-scope/leave/verify-context-in-for-loop-block -language/block-scope/leave/verify-context-in-labelled-block -language/block-scope/leave/verify-context-in-try-block -language/block-scope/leave/x-after-break-to-label -language/block-scope/leave/x-before-continue -language/block-scope/shadowing/const-declaration-shadowing-catch-parameter -language/block-scope/shadowing/const-declarations-shadowing-parameter-name-let-const-and-var-variables -language/block-scope/shadowing/let-declaration-shadowing-catch-parameter -language/block-scope/shadowing/let-declarations-shadowing-parameter-name-let-const-and-var +language/block-scope/syntax/for-in/acquire-properties-from-array +language/block-scope/syntax/for-in/acquire-properties-from-object language/block-scope/syntax/for-in/mixed-values-in-iteration language/computed-property-names/basics/string language/computed-property-names/basics/symbol @@ -4004,7 +3987,6 @@ language/destructuring/binding/syntax/recursive-array-and-object-patterns language/eval-code/direct/lex-env-distinct-cls language/eval-code/direct/lex-env-distinct-const language/eval-code/direct/lex-env-distinct-let -language/eval-code/direct/lex-env-heritage language/eval-code/direct/lex-env-no-init-cls language/eval-code/direct/lex-env-no-init-const language/eval-code/direct/lex-env-no-init-let @@ -4026,7 +4008,6 @@ language/eval-code/indirect/always-non-strict language/eval-code/indirect/lex-env-distinct-cls language/eval-code/indirect/lex-env-distinct-const language/eval-code/indirect/lex-env-distinct-let -language/eval-code/indirect/lex-env-heritage language/eval-code/indirect/lex-env-no-init-cls language/eval-code/indirect/lex-env-no-init-const language/eval-code/indirect/lex-env-no-init-let @@ -6186,14 +6167,12 @@ language/expressions/yield/star-rhs-unresolvable language/expressions/yield/star-string language/expressions/yield/then-return language/expressions/yield/within-for -language/function-code/block-decl-strict language/function-code/each-param-has-own-non-shared-eval-scope language/function-code/each-param-has-own-scope language/function-code/eval-param-env-with-computed-key language/function-code/eval-param-env-with-prop-initializer language/function-code/switch-case-decl-strict language/function-code/switch-dflt-decl-strict -language/global-code/block-decl-strict language/global-code/decl-lex language/global-code/decl-lex-configurable-global language/global-code/decl-lex-deletion @@ -6442,8 +6421,6 @@ language/statements/async-function/try-return-finally-throw language/statements/async-function/try-throw-finally-reject language/statements/async-function/try-throw-finally-return language/statements/async-function/try-throw-finally-throw -language/statements/block/scope-lex-close -language/statements/block/scope-lex-open language/statements/block/tco-stmt language/statements/block/tco-stmt-list language/statements/class/accessor-name-inst-computed @@ -7445,12 +7422,10 @@ language/statements/const/function-local-closure-get-before-initialization language/statements/const/function-local-use-before-initialization-in-declaration-statement language/statements/const/function-local-use-before-initialization-in-prior-statement language/statements/const/global-closure-get-before-initialization -language/statements/const/syntax/const language/statements/const/syntax/const-invalid-assignment-next-expression-for language/statements/const/syntax/const-invalid-assignment-statement-body-for-in language/statements/const/syntax/const-invalid-assignment-statement-body-for-of language/statements/const/syntax/const-outer-inner-let-bindings -language/statements/continue/shadowing-loop-variable-in-same-scope-as-continue language/statements/do-while/tco-body language/statements/empty/cptn-value language/statements/for/dstr-const-ary-init-iter-close @@ -8697,7 +8672,6 @@ language/statements/try/dstr-obj-ptrn-list-err language/statements/try/dstr-obj-ptrn-prop-eval-err language/statements/try/dstr-obj-ptrn-prop-id-get-value-err language/statements/try/dstr-obj-ptrn-prop-id-init-throws -language/statements/try/scope-catch-block-lex-close language/statements/try/scope-catch-block-lex-open language/statements/try/scope-catch-param-lex-open language/statements/try/scope-catch-param-var-none @@ -8776,14 +8750,11 @@ built-ins/NativeErrors/SyntaxError/is-error-object built-ins/NativeErrors/TypeError/is-error-object built-ins/NativeErrors/URIError/is-error-object -built-ins/Object/prototype/toLocaleString/primitive_this_value language/expressions/object/yield-non-strict-access language/expressions/object/yield-non-strict-syntax language/expressions/object/not-defined language/expressions/object/prop-def-id-get-error language/expressions/object/prop-def-id-valid -language/function-code/10.4.3-1-104 -language/function-code/10.4.3-1-106 # Expected to fail but passed --- language/eval-code/direct/var-env-global-lex-non-strict @@ -8791,4 +8762,4 @@ language/global-code/decl-lex-restricted-global language/statements/const/global-use-before-initialization-in-declaration-statement language/statements/const/global-use-before-initialization-in-prior-statement language/statements/let/global-use-before-initialization-in-declaration-statement -language/statements/let/global-use-before-initialization-in-prior-statement
\ No newline at end of file +language/statements/let/global-use-before-initialization-in-prior-statement diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index a57e0b2c79..4945a23302 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -3209,9 +3209,7 @@ void tst_QJSEngine::threadedEngine() void tst_QJSEngine::functionDeclarationsInConditionals() { - // Even though this is bad practice (and test262 covers it with best practices test cases), - // we do allow for function declarations in if and while statements, as unfortunately that's - // real world JavaScript. (QTBUG-33064 for example) + // Visibility of function declarations inside blocks is limited to the block QJSEngine eng; QJSValue result = eng.evaluate("if (true) {\n" " function blah() { return false; }\n" @@ -3219,8 +3217,7 @@ void tst_QJSEngine::functionDeclarationsInConditionals() " function blah() { return true; }\n" "}\n" "blah();"); - QVERIFY(result.isBool()); - QCOMPARE(result.toBool(), true); + QVERIFY(result.isError()); } void tst_QJSEngine::arrayPop_QTBUG_35979() |