From 5747a7530206ac410b6bd7c1b8490d7d389ad3a5 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Fri, 27 Apr 2018 11:41:13 +0200 Subject: Add Generator support Add support for ES6 generators. Those are currently always executed in the interpreter (we never JIT them), to simplify the initial implementation. Most functionality, except for 'yield *' expressions are supported. 'yield *' will have to wait until we support for(... of ...) Change-Id: I7c059d1e3b301cbcb79e3746b4bec346738fd426 Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4codegen.cpp | 66 +++++++-- src/qml/compiler/qv4codegen_p.h | 3 + src/qml/compiler/qv4instr_moth.cpp | 7 + src/qml/compiler/qv4instr_moth_p.h | 4 + src/qml/jit/qv4jit.cpp | 19 +++ src/qml/jit/qv4jit_p.h | 3 + src/qml/jsruntime/jsruntime.pri | 2 + src/qml/jsruntime/qv4engine.cpp | 11 +- src/qml/jsruntime/qv4engine_p.h | 8 +- src/qml/jsruntime/qv4enginebase_p.h | 2 + src/qml/jsruntime/qv4function_p.h | 1 + src/qml/jsruntime/qv4functionobject.cpp | 51 ++++--- src/qml/jsruntime/qv4functionobject_p.h | 6 + src/qml/jsruntime/qv4generatorobject.cpp | 247 +++++++++++++++++++++++++++++++ src/qml/jsruntime/qv4generatorobject_p.h | 136 +++++++++++++++++ src/qml/jsruntime/qv4managed.cpp | 3 + src/qml/jsruntime/qv4managed_p.h | 1 + src/qml/jsruntime/qv4runtime.cpp | 3 + src/qml/jsruntime/qv4vme_moth.cpp | 30 +++- src/qml/parser/qqmljs.g | 13 +- 20 files changed, 566 insertions(+), 50 deletions(-) create mode 100644 src/qml/jsruntime/qv4generatorobject.cpp create mode 100644 src/qml/jsruntime/qv4generatorobject_p.h (limited to 'src') diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 5cfa70c03f..c5394e16c6 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -2271,7 +2271,28 @@ bool Codegen::visit(FunctionDeclaration * ast) bool Codegen::visit(YieldExpression *ast) { - throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Support for 'yield' unimplemented.")); + if (ast->isYieldStar) { + throwSyntaxError(ast->firstSourceLocation(), QLatin1String("yield* is not currently supported")); + return false; + } + if (inFormalParameterList) { + throwSyntaxError(ast->firstSourceLocation(), QLatin1String("yield is not allowed inside parameter lists")); + return false; + } + + + Reference result = ast->expression ? expression(ast->expression) : Reference::fromConst(this, Encode::undefined()); + if (hasError) + return false; + result.loadInAccumulator(); + Instruction::Yield yield; + bytecodeGenerator->addInstruction(yield); + Instruction::Resume resume; + BytecodeGenerator::Jump jump = bytecodeGenerator->addJumpInstruction(resume); + Reference acc = Reference::fromAccumulator(this); + emitReturn(acc); + jump.link(); + _expr.setResult(acc); return false; } @@ -2309,10 +2330,6 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, { enterContext(ast); - if (_context->isGenerator) { - throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Support for generator functions unimplemented.")); - } - if (_context->functionIndex >= 0) // already defined return leaveContext(); @@ -2345,6 +2362,9 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, // reserve the js stack frame (Context & js Function & accumulator) bytecodeGenerator->newRegisterArray(sizeof(CallData)/sizeof(Value) - 1 + _context->arguments.size()); + bool _inFormalParameterList = false; + qSwap(_inFormalParameterList, inFormalParameterList); + int returnAddress = -1; bool _requiresReturnValue = _context->requiresImplicitReturnValue(); qSwap(requiresReturnValue, _requiresReturnValue); @@ -2355,6 +2375,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, RegisterScope registerScope(this); _context->emitBlockHeader(this); + inFormalParameterList = true; int argc = 0; while (formals) { PatternElement *e = formals->element; @@ -2382,6 +2403,12 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, formals = formals->next; ++argc; } + inFormalParameterList = false; + + if (_context->isGenerator) { + Instruction::Yield yield; + bytecodeGenerator->addInstruction(yield); + } beginFunctionBodyHook(); @@ -2415,6 +2442,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, qSwap(_returnAddress, returnAddress); qSwap(requiresReturnValue, _requiresReturnValue); + qSwap(_inFormalParameterList, inFormalParameterList); bytecodeGenerator = savedBytecodeGenerator; controlFlow = savedControlFlow; _functionContext = savedFunctionContext; @@ -2781,6 +2809,21 @@ bool Codegen::visit(LocalForStatement *ast) return false; } +void Codegen::emitReturn(const Reference &expr) +{ + if (controlFlow && controlFlow->returnRequiresUnwind()) { + if (_returnAddress >= 0) + (void) expr.storeOnStack(_returnAddress); + else + expr.loadInAccumulator(); + ControlFlow::Handler h = controlFlow->getHandler(ControlFlow::Return); + controlFlow->jumpToHandler(h); + } else { + expr.loadInAccumulator(); + bytecodeGenerator->addInstruction(Instruction::Ret()); + } +} + bool Codegen::visit(ReturnStatement *ast) { if (hasError) @@ -2799,17 +2842,8 @@ bool Codegen::visit(ReturnStatement *ast) expr = Reference::fromConst(this, Encode::undefined()); } - if (controlFlow && controlFlow->returnRequiresUnwind()) { - if (_returnAddress >= 0) - (void) expr.storeOnStack(_returnAddress); - else - expr.loadInAccumulator(); - ControlFlow::Handler h = controlFlow->getHandler(ControlFlow::Return); - controlFlow->jumpToHandler(h); - } else { - expr.loadInAccumulator(); - bytecodeGenerator->addInstruction(Instruction::Ret()); - } + emitReturn(expr); + return false; } diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 3c64e0f00e..e14d6b16e3 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -521,6 +521,8 @@ protected: virtual Reference fallbackNameLookup(const QString &name); virtual void beginFunctionBodyHook() {} + void emitReturn(const Reference &expr); + // nodes bool visit(AST::ArgumentList *ast) override; bool visit(AST::CaseBlock *ast) override; @@ -670,6 +672,7 @@ protected: bool _strictMode; bool useFastLookups = true; bool requiresReturnValue = false; + bool inFormalParameterList = false; ControlFlow *controlFlow = nullptr; bool _fileNameIsUrl; diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index bae8d9e64e..49d8455617 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -341,6 +341,13 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st d << dumpRegister(base, nFormals) << "[" << index << "]"; MOTH_END_INSTR(LoadIdObject) + MOTH_BEGIN_INSTR(Yield) + MOTH_END_INSTR(Yield) + + MOTH_BEGIN_INSTR(Resume) + d << ABSOLUTE_OFFSET(); + MOTH_END_INSTR(Resume) + MOTH_BEGIN_INSTR(CallValue) d << dumpRegister(name, nFormals) << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallValue) diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index 15ab1b52dd..2e3d118e52 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -91,6 +91,8 @@ QT_BEGIN_NAMESPACE #define INSTR_LoadScopeObjectProperty(op) INSTRUCTION(op, LoadScopeObjectProperty, 3, propertyIndex, base, captureRequired) #define INSTR_LoadContextObjectProperty(op) INSTRUCTION(op, LoadContextObjectProperty, 3, propertyIndex, base, captureRequired) #define INSTR_LoadIdObject(op) INSTRUCTION(op, LoadIdObject, 2, index, base) +#define INSTR_Yield(op) INSTRUCTION(op, Yield, 0) +#define INSTR_Resume(op) INSTRUCTION(op, Resume, 1, offset) #define INSTR_StoreProperty(op) INSTRUCTION(op, StoreProperty, 2, name, base) #define INSTR_SetLookup(op) INSTRUCTION(op, SetLookup, 2, index, base) #define INSTR_StoreScopeObjectProperty(op) INSTRUCTION(op, StoreScopeObjectProperty, 2, base, propertyIndex) @@ -220,6 +222,8 @@ QT_BEGIN_NAMESPACE F(LoadScopeObjectProperty) \ F(LoadContextObjectProperty) \ F(LoadIdObject) \ + F(Yield) \ + F(Resume) \ F(CallValue) \ F(CallProperty) \ F(CallPropertyLookup) \ diff --git a/src/qml/jit/qv4jit.cpp b/src/qml/jit/qv4jit.cpp index 678a0c16db..87c3642479 100644 --- a/src/qml/jit/qv4jit.cpp +++ b/src/qml/jit/qv4jit.cpp @@ -40,6 +40,7 @@ #include "qv4jit_p.h" #include "qv4assembler_p.h" #include +#include #ifdef V4_ENABLE_JIT @@ -462,6 +463,18 @@ void BaselineJIT::generate_LoadIdObject(int index, int base) as->checkException(); } +void BaselineJIT::generate_Yield() +{ + // ##### + Q_UNREACHABLE(); +} + +void BaselineJIT::generate_Resume(int) +{ + // ##### + Q_UNREACHABLE(); +} + void BaselineJIT::generate_CallValue(int name, int argc, int argv) { STORE_IP(); @@ -1146,6 +1159,12 @@ void BaselineJIT::collectLabelsInBytecode() MOTH_BEGIN_INSTR(LoadIdObject) MOTH_END_INSTR(LoadIdObject) + MOTH_BEGIN_INSTR(Yield) + MOTH_END_INSTR(Yield) + + MOTH_BEGIN_INSTR(Resume) + MOTH_END_INSTR(Resume) + MOTH_BEGIN_INSTR(CallValue) MOTH_END_INSTR(CallValue) diff --git a/src/qml/jit/qv4jit_p.h b/src/qml/jit/qv4jit_p.h index 150f4b6aab..bf11e69cbb 100644 --- a/src/qml/jit/qv4jit_p.h +++ b/src/qml/jit/qv4jit_p.h @@ -161,6 +161,9 @@ public: void generate_LoadContextObjectProperty(int propertyIndex, int base, int captureRequired) override; void generate_LoadIdObject(int index, int base) override; + void generate_Yield() override; + void generate_Resume(int) override; + void generate_CallValue(int name, int argc, int argv) override; void generate_CallProperty(int name, int base, int argc, int argv) override; void generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv) override; diff --git a/src/qml/jsruntime/jsruntime.pri b/src/qml/jsruntime/jsruntime.pri index d06e505c81..96956e2613 100644 --- a/src/qml/jsruntime/jsruntime.pri +++ b/src/qml/jsruntime/jsruntime.pri @@ -21,6 +21,7 @@ SOURCES += \ $$PWD/qv4errorobject.cpp \ $$PWD/qv4function.cpp \ $$PWD/qv4functionobject.cpp \ + $$PWD/qv4generatorobject.cpp \ $$PWD/qv4globalobject.cpp \ $$PWD/qv4iterator.cpp \ $$PWD/qv4jsonobject.cpp \ @@ -74,6 +75,7 @@ HEADERS += \ $$PWD/qv4errorobject_p.h \ $$PWD/qv4function_p.h \ $$PWD/qv4functionobject_p.h \ + $$PWD/qv4generatorobject_p.h \ $$PWD/qv4globalobject_p.h \ $$PWD/qv4iterator_p.h \ $$PWD/qv4jsonobject_p.h \ diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 1c3c3e7ff8..90c5272b7f 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -67,6 +67,7 @@ #include "qv4executableallocator_p.h" #include "qv4iterator_p.h" #include "qv4stringiterator_p.h" +#include "qv4generatorobject_p.h" #if QT_CONFIG(qml_sequence_object) #include "qv4sequenceobject_p.h" @@ -325,11 +326,17 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) ic = ic->addMember(id_name()->identifier(), Attr_ReadOnly, &index); Q_ASSERT(index == Heap::ScriptFunction::Index_Name); ic = ic->changeVTable(ScriptFunction::staticVTable()); - classes[Class_ScriptFunction] = ic->addMember(id_length()->identifier(), Attr_ReadOnly, &index); + ic = ic->addMember(id_length()->identifier(), Attr_ReadOnly, &index); Q_ASSERT(index == Heap::ScriptFunction::Index_Length); + classes[Class_ScriptFunction] = ic->d(); + ic = ic->changeVTable(GeneratorFunction::staticVTable()); + classes[Class_GeneratorFunction] = ic->d(); classes[Class_ObjectProto] = classes[Class_Object]->addMember(id_constructor()->identifier(), Attr_NotEnumerable, &index); Q_ASSERT(index == Heap::FunctionObject::Index_ProtoConstructor); + jsObjects[GeneratorProto] = memoryManager->allocObject(classes[Class_Object]); + classes[Class_GeneratorObject] = newInternalClass(QV4::GeneratorObject::staticVTable(), generatorPrototype()); + ScopedString str(scope); classes[Class_RegExp] = classes[Class_Empty]->changeVTable(QV4::RegExp::staticVTable()); ic = newInternalClass(QV4::RegExpObject::staticVTable(), objectPrototype()); @@ -397,6 +404,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) jsObjects[Boolean_Ctor] = memoryManager->allocate(global); jsObjects[Array_Ctor] = memoryManager->allocate(global); jsObjects[Function_Ctor] = memoryManager->allocate(global); + jsObjects[GeneratorFunction_Ctor] = memoryManager->allocate(global); jsObjects[Date_Ctor] = memoryManager->allocate(global); jsObjects[RegExp_Ctor] = memoryManager->allocate(global); jsObjects[Error_Ctor] = memoryManager->allocate(global); @@ -419,6 +427,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) static_cast(propertyListPrototype())->init(this); static_cast(datePrototype())->init(this, dateCtor()); static_cast(functionPrototype())->init(this, functionCtor()); + static_cast(generatorPrototype())->init(this, generatorFunctionCtor()); static_cast(regExpPrototype())->init(this, regExpCtor()); static_cast(errorPrototype())->init(this, errorCtor()); static_cast(evalErrorPrototype())->init(this, evalErrorCtor()); diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index 819481ed91..64bbd1163f 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -96,6 +96,8 @@ struct Q_QML_EXPORT CppStackFrame { const Value *originalArguments; int originalArgumentsCount; int instructionPointer; + const uchar *yield; + const uchar *exceptionHandler; QString source() const; QString function() const; @@ -170,6 +172,7 @@ public: BooleanProto, DateProto, FunctionProto, + GeneratorProto, RegExpProto, ErrorProto, EvalErrorProto, @@ -197,6 +200,7 @@ public: Boolean_Ctor, Array_Ctor, Function_Ctor, + GeneratorFunction_Ctor, Date_Ctor, RegExp_Ctor, Error_Ctor, @@ -225,6 +229,7 @@ public: FunctionObject *booleanCtor() const { return reinterpret_cast(jsObjects + Boolean_Ctor); } FunctionObject *arrayCtor() const { return reinterpret_cast(jsObjects + Array_Ctor); } FunctionObject *functionCtor() const { return reinterpret_cast(jsObjects + Function_Ctor); } + FunctionObject *generatorFunctionCtor() const { return reinterpret_cast(jsObjects + GeneratorFunction_Ctor); } FunctionObject *dateCtor() const { return reinterpret_cast(jsObjects + Date_Ctor); } FunctionObject *regExpCtor() const { return reinterpret_cast(jsObjects + RegExp_Ctor); } FunctionObject *errorCtor() const { return reinterpret_cast(jsObjects + Error_Ctor); } @@ -248,6 +253,7 @@ public: Object *booleanPrototype() const { return reinterpret_cast(jsObjects + BooleanProto); } Object *datePrototype() const { return reinterpret_cast(jsObjects + DateProto); } Object *functionPrototype() const { return reinterpret_cast(jsObjects + FunctionProto); } + Object *generatorPrototype() const { return reinterpret_cast(jsObjects + GeneratorProto); } Object *regExpPrototype() const { return reinterpret_cast(jsObjects + RegExpProto); } Object *errorPrototype() const { return reinterpret_cast(jsObjects + ErrorProto); } Object *evalErrorPrototype() const { return reinterpret_cast(jsObjects + EvalErrorProto); } @@ -539,7 +545,7 @@ public: if (!m_canAllocateExecutableMemory) return false; if (f) - return f->interpreterCallCount >= jitCallCountThreshold; + return !f->isGenerator() && f->interpreterCallCount >= jitCallCountThreshold; return true; #else Q_UNUSED(f); diff --git a/src/qml/jsruntime/qv4enginebase_p.h b/src/qml/jsruntime/qv4enginebase_p.h index d1257b6248..03ff25d5b5 100644 --- a/src/qml/jsruntime/qv4enginebase_p.h +++ b/src/qml/jsruntime/qv4enginebase_p.h @@ -100,6 +100,8 @@ struct Q_QML_EXPORT EngineBase { Class_Object, Class_ArrayObject, Class_FunctionObject, + Class_GeneratorFunction, + Class_GeneratorObject, Class_StringObject, Class_SymbolObject, Class_ScriptFunction, diff --git a/src/qml/jsruntime/qv4function_p.h b/src/qml/jsruntime/qv4function_p.h index dc18245819..fa6b886b10 100644 --- a/src/qml/jsruntime/qv4function_p.h +++ b/src/qml/jsruntime/qv4function_p.h @@ -100,6 +100,7 @@ struct Q_QML_EXPORT Function { inline bool isStrict() const { return compiledFunction->flags & CompiledData::Function::IsStrict; } inline bool isArrowFunction() const { return compiledFunction->flags & CompiledData::Function::IsArrowFunction; } + inline bool isGenerator() const { return compiledFunction->flags & CompiledData::Function::IsGenerator; } QQmlSourceLocation sourceLocation() const { diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp index a684e1c5d3..6382a6e862 100644 --- a/src/qml/jsruntime/qv4functionobject.cpp +++ b/src/qml/jsruntime/qv4functionobject.cpp @@ -214,10 +214,8 @@ void Heap::FunctionCtor::init(QV4::ExecutionContext *scope) } // 15.3.2 -ReturnedValue FunctionCtor::callAsConstructor(const FunctionObject *f, const Value *argv, int argc) +QQmlRefPointer FunctionCtor::parse(ExecutionEngine *engine, const Value *argv, int argc, Type t) { - Scope scope(f->engine()); - QString arguments; QString body; if (argc > 0) { @@ -228,38 +226,51 @@ ReturnedValue FunctionCtor::callAsConstructor(const FunctionObject *f, const Val } body = argv[argc - 1].toQString(); } - if (scope.engine->hasException) - return Encode::undefined(); + if (engine->hasException) + return nullptr; - QString function = QLatin1String("function(") + arguments + QLatin1String("){") + body + QLatin1Char('}'); + QString function = (t == Type_Function ? QLatin1String("function(") : QLatin1String("function*(")) + arguments + QLatin1String("){") + body + QLatin1Char('}'); - QQmlJS::Engine ee, *engine = ⅇ - QQmlJS::Lexer lexer(engine); + QQmlJS::Engine ee; + QQmlJS::Lexer lexer(&ee); lexer.setCode(function, 1, false); - QQmlJS::Parser parser(engine); + QQmlJS::Parser parser(&ee); const bool parsed = parser.parseExpression(); - if (!parsed) - return scope.engine->throwSyntaxError(QLatin1String("Parse error")); + if (!parsed) { + engine->throwSyntaxError(QLatin1String("Parse error")); + return nullptr; + } QQmlJS::AST::FunctionExpression *fe = QQmlJS::AST::cast(parser.rootNode()); - if (!fe) - return scope.engine->throwSyntaxError(QLatin1String("Parse error")); + if (!fe) { + engine->throwSyntaxError(QLatin1String("Parse error")); + return nullptr; + } - Compiler::Module module(scope.engine->debugger() != nullptr); + Compiler::Module module(engine->debugger() != nullptr); Compiler::JSUnitGenerator jsGenerator(&module); - RuntimeCodegen cg(scope.engine, &jsGenerator, false); + RuntimeCodegen cg(engine, &jsGenerator, false); cg.generateFromFunctionExpression(QString(), function, fe, &module); - if (scope.hasException()) - return Encode::undefined(); + if (engine->hasException) + return nullptr; + + return cg.generateCompilationUnit(); +} - QQmlRefPointer compilationUnit = cg.generateCompilationUnit(); - Function *vmf = compilationUnit->linkToEngine(scope.engine); +ReturnedValue FunctionCtor::callAsConstructor(const FunctionObject *f, const Value *argv, int argc) +{ + ExecutionEngine *engine = f->engine(); + + QQmlRefPointer compilationUnit = parse(engine, argv, argc, Type_Function); + if (engine->hasException) + return Encode::undefined(); - ExecutionContext *global = scope.engine->rootContext(); + Function *vmf = compilationUnit->linkToEngine(engine); + ExecutionContext *global = engine->rootContext(); return Encode(FunctionObject::createScriptFunction(global, vmf)); } diff --git a/src/qml/jsruntime/qv4functionobject_p.h b/src/qml/jsruntime/qv4functionobject_p.h index c9efee1a46..e8bd574161 100644 --- a/src/qml/jsruntime/qv4functionobject_p.h +++ b/src/qml/jsruntime/qv4functionobject_p.h @@ -186,6 +186,12 @@ struct FunctionCtor: FunctionObject static ReturnedValue callAsConstructor(const FunctionObject *f, const Value *argv, int argc); static ReturnedValue call(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +protected: + enum Type { + Type_Function, + Type_Generator + }; + static QQmlRefPointer parse(ExecutionEngine *engine, const Value *argv, int argc, Type t = Type_Function); }; struct FunctionPrototype: FunctionObject diff --git a/src/qml/jsruntime/qv4generatorobject.cpp b/src/qml/jsruntime/qv4generatorobject.cpp new file mode 100644 index 0000000000..4339a95f45 --- /dev/null +++ b/src/qml/jsruntime/qv4generatorobject.cpp @@ -0,0 +1,247 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(GeneratorFunctionCtor); +DEFINE_OBJECT_VTABLE(GeneratorFunction); +DEFINE_OBJECT_VTABLE(GeneratorObject); + +void Heap::GeneratorFunctionCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("GeneratorFunction")); +} + +ReturnedValue GeneratorFunctionCtor::callAsConstructor(const FunctionObject *f, const Value *argv, int argc) +{ + ExecutionEngine *engine = f->engine(); + + QQmlRefPointer compilationUnit = parse(engine, argv, argc, Type_Generator); + if (engine->hasException) + return Encode::undefined(); + + Function *vmf = compilationUnit->linkToEngine(engine); + ExecutionContext *global = engine->rootContext(); + return Encode(GeneratorFunction::create(global, vmf)); +} + +// 15.3.1: This is equivalent to new Function(...) +ReturnedValue GeneratorFunctionCtor::call(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + return callAsConstructor(f, argv, argc); +} + +Heap::FunctionObject *GeneratorFunction::create(ExecutionContext *context, Function *function) +{ + Scope scope(context); + Scoped g(scope, context->engine()->memoryManager->allocate(context, function)); + ScopedObject proto(scope, scope.engine->newObject()); + proto->setPrototype(scope.engine->generatorPrototype()); + g->defineDefaultProperty(scope.engine->id_prototype(), proto, Attr_NotConfigurable|Attr_NotEnumerable); + g->setPrototype(ScopedObject(scope, scope.engine->generatorFunctionCtor()->get(scope.engine->id_prototype()))); + return g->d(); +} + +ReturnedValue GeneratorFunction::callAsConstructor(const FunctionObject *f, const Value *, int) +{ + return f->engine()->throwTypeError(); +} + +ReturnedValue GeneratorFunction::call(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + const GeneratorFunction *gf = static_cast(f); + Function *function = gf->function(); + ExecutionEngine *engine = gf->engine(); + + // We need to set up a separate stack for the generator, as it's being re-entered + uint stackSize = argc; // space for the original arguments + int jsStackFrameSize = offsetof(CallData, args)/sizeof(Value) + function->compiledFunction->nRegisters; + stackSize += jsStackFrameSize; + + size_t requiredMemory = sizeof(GeneratorObject::Data) - sizeof(Value) + sizeof(Value) * stackSize; + + Scope scope(gf); + Scoped g(scope, scope.engine->memoryManager->allocManaged(requiredMemory, scope.engine->classes[EngineBase::Class_GeneratorObject])); + g->setPrototype(ScopedObject(scope, gf->get(scope.engine->id_prototype()))); + + Heap::GeneratorObject *gp = g->d(); + gp->stack.size = stackSize; + gp->stack.alloc = stackSize; + + // copy original arguments + memcpy(gp->stack.values, argv, argc*sizeof(Value)); + gp->cppFrame.originalArguments = gp->stack.values; + gp->cppFrame.originalArgumentsCount = argc; + + // setup JS stack frame + CallData *callData = reinterpret_cast(&gp->stack.values[argc]); + callData->function = *gf; + callData->context = gf->scope(); + callData->accumulator = Encode::undefined(); + callData->thisObject = thisObject ? *thisObject : Primitive::undefinedValue(); + if (argc > int(function->nFormals)) + argc = int(function->nFormals); + callData->setArgc(argc); + memcpy(callData->args, argv, argc*sizeof(Value)); + + gp->cppFrame.v4Function = function; + gp->cppFrame.instructionPointer = 0; + gp->cppFrame.jsFrame = callData; + gp->cppFrame.parent = engine->currentStackFrame; + engine->currentStackFrame = &gp->cppFrame; + + Moth::VME::interpret(gp->cppFrame, function->codeData); + gp->state = GeneratorState::SuspendedStart; + + engine->currentStackFrame = gp->cppFrame.parent; + return g->asReturnedValue(); +} + + +void Heap::GeneratorPrototype::init() +{ + Heap::FunctionObject::init(); +} + + +void GeneratorPrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedValue v(scope); + + ScopedObject ctorProto(scope, engine->newObject(engine->newInternalClass(Object::staticVTable(), engine->functionPrototype()))); + + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Primitive::fromInt32(1)); + ctor->defineReadonlyProperty(engine->id_prototype(), ctorProto); + + ctorProto->defineDefaultProperty(QStringLiteral("constructor"), (v = ctor), Attr_ReadOnly_ButConfigurable); + ctorProto->defineDefaultProperty(engine->symbol_toStringTag(), (v = engine->newIdentifier(QStringLiteral("GeneratorFunction"))), Attr_ReadOnly_ButConfigurable); + ctorProto->defineDefaultProperty(engine->id_prototype(), (v = this), Attr_ReadOnly_ButConfigurable); + + defineDefaultProperty(QStringLiteral("constructor"), ctorProto, Attr_ReadOnly_ButConfigurable); + defineDefaultProperty(QStringLiteral("next"), method_next, 1); + defineDefaultProperty(QStringLiteral("return"), method_return, 1); + defineDefaultProperty(QStringLiteral("throw"), method_throw, 1); + defineDefaultProperty(engine->symbol_toStringTag(), (v = engine->newString(QStringLiteral("Generator"))), Attr_ReadOnly_ButConfigurable); +} + +ReturnedValue GeneratorPrototype::method_next(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *engine = f->engine(); + const GeneratorObject *g = thisObject->as(); + if (!g || g->d()->state == GeneratorState::Executing) + return engine->throwTypeError(); + Heap::GeneratorObject *gp = g->d(); + + if (gp->state == GeneratorState::Completed) + return IteratorPrototype::createIterResultObject(engine, Primitive::undefinedValue(), true); + + return g->resume(engine, argc ? argv[0] : Primitive::undefinedValue()); +} + +ReturnedValue GeneratorPrototype::method_return(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *engine = f->engine(); + const GeneratorObject *g = thisObject->as(); + if (!g || g->d()->state == GeneratorState::Executing) + return engine->throwTypeError(); + + Heap::GeneratorObject *gp = g->d(); + + if (gp->state == GeneratorState::SuspendedStart) + gp->state = GeneratorState::Completed; + + if (gp->state == GeneratorState::Completed) + return IteratorPrototype::createIterResultObject(engine, argc ? argv[0] : Primitive::undefinedValue(), true); + + // the bytecode interpreter interprets an exception with empty value as + // a yield called with return() + engine->throwError(Primitive::emptyValue()); + + return g->resume(engine, argc ? argv[0]: Primitive::undefinedValue()); +} + +ReturnedValue GeneratorPrototype::method_throw(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *engine = f->engine(); + const GeneratorObject *g = thisObject->as(); + if (!g || g->d()->state == GeneratorState::Executing) + return engine->throwTypeError(); + + Heap::GeneratorObject *gp = g->d(); + + engine->throwError(argc ? argv[0]: Primitive::undefinedValue()); + + if (gp->state == GeneratorState::SuspendedStart || gp->state == GeneratorState::Completed) { + gp->state = GeneratorState::Completed; + return Encode::undefined(); + } + + return g->resume(engine, Primitive::undefinedValue()); +} + +ReturnedValue GeneratorObject::resume(ExecutionEngine *engine, const Value &arg) const +{ + Heap::GeneratorObject *gp = d(); + gp->state = GeneratorState::Executing; + gp->cppFrame.parent = engine->currentStackFrame; + engine->currentStackFrame = &gp->cppFrame; + + Q_ASSERT(gp->cppFrame.yield != nullptr); + const uchar *code = gp->cppFrame.yield; + gp->cppFrame.yield = nullptr; + gp->cppFrame.jsFrame->accumulator = arg; + + Scope scope(engine); + ScopedValue result(scope, Moth::VME::interpret(gp->cppFrame, code)); + + engine->currentStackFrame = gp->cppFrame.parent; + + bool done = (gp->cppFrame.yield == nullptr); + gp->state = done ? GeneratorState::Completed : GeneratorState::SuspendedYield; + if (engine->hasException) + return Encode::undefined(); + return IteratorPrototype::createIterResultObject(engine, result, done); +} diff --git a/src/qml/jsruntime/qv4generatorobject_p.h b/src/qml/jsruntime/qv4generatorobject_p.h new file mode 100644 index 0000000000..62ffcbbad1 --- /dev/null +++ b/src/qml/jsruntime/qv4generatorobject_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4GENERATOROBJECT_P_H +#define QV4GENERATOROBJECT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4functionobject_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +enum class GeneratorState { + Undefined, + SuspendedStart, + SuspendedYield, + Executing, + Completed +}; + +namespace Heap { + +struct GeneratorFunctionCtor : FunctionObject { + void init(QV4::ExecutionContext *scope); +}; + +struct GeneratorFunction : ScriptFunction { +}; + +struct GeneratorPrototype : FunctionObject { + void init(); +}; + +#define GeneratorObjectMembers(class, Member) \ + Member(class, Pointer, ExecutionContext *, context) \ + Member(class, Pointer, GeneratorFunction *, function) \ + Member(class, NoMark, GeneratorState, state) \ + Member(class, NoMark, CppStackFrame, cppFrame) \ + Member(class, ValueArray, ValueArray, stack) + +DECLARE_HEAP_OBJECT(GeneratorObject, Object) { + DECLARE_MARKOBJECTS(GeneratorObject); +}; + +} + +struct GeneratorFunctionCtor : FunctionCtor +{ + V4_OBJECT2(GeneratorFunctionCtor, FunctionCtor) + + static ReturnedValue callAsConstructor(const FunctionObject *f, const Value *argv, int argc); + static ReturnedValue call(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct GeneratorFunction : ScriptFunction +{ + V4_OBJECT2(GeneratorFunction, ScriptFunction) + V4_INTERNALCLASS(GeneratorFunction) + + static Heap::FunctionObject *create(ExecutionContext *scope, Function *function); + static ReturnedValue callAsConstructor(const FunctionObject *f, const Value *argv, int argc); + static ReturnedValue call(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct GeneratorPrototype : Object +{ + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_next(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_return(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_throw(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + + +struct GeneratorObject : Object { + V4_OBJECT2(GeneratorObject, Object) + Q_MANAGED_TYPE(GeneratorObject) + V4_INTERNALCLASS(GeneratorObject) + V4_PROTOTYPE(generatorPrototype) + + ReturnedValue resume(ExecutionEngine *engine, const Value &arg) const; +}; + +} + +QT_END_NAMESPACE + +#endif // QV4GENERATORFUNCTION_P_H + diff --git a/src/qml/jsruntime/qv4managed.cpp b/src/qml/jsruntime/qv4managed.cpp index 92c5a1a069..f96e3c87d8 100644 --- a/src/qml/jsruntime/qv4managed.cpp +++ b/src/qml/jsruntime/qv4managed.cpp @@ -88,6 +88,9 @@ QString Managed::className() const case Type_FunctionObject: s = "Function"; break; + case Type_GeneratorObject: + s = "Generator"; + break; case Type_BooleanObject: s = "Boolean"; break; diff --git a/src/qml/jsruntime/qv4managed_p.h b/src/qml/jsruntime/qv4managed_p.h index b60602b34c..1b67aacd7b 100644 --- a/src/qml/jsruntime/qv4managed_p.h +++ b/src/qml/jsruntime/qv4managed_p.h @@ -185,6 +185,7 @@ public: Type_Symbol, Type_ArrayObject, Type_FunctionObject, + Type_GeneratorObject, Type_BooleanObject, Type_NumberObject, Type_StringObject, diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index 135da55daf..3ddd543b40 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -62,6 +62,7 @@ #include #include "qv4qobjectwrapper_p.h" #include "qv4symbol_p.h" +#include "qv4generatorobject_p.h" #include #endif @@ -315,6 +316,8 @@ ReturnedValue Runtime::method_closure(ExecutionEngine *engine, int functionId) QV4::Function *clos = static_cast(engine->currentStackFrame->v4Function->compilationUnit)->runtimeFunctions[functionId]; Q_ASSERT(clos); ExecutionContext *current = static_cast(&engine->currentStackFrame->jsFrame->context); + if (clos->isGenerator()) + return GeneratorFunction::create(current, clos)->asReturnedValue(); return FunctionObject::createScriptFunction(current, clos)->asReturnedValue(); } diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 17255c7e9e..08a085c90d 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include @@ -508,6 +509,8 @@ QV4::ReturnedValue VME::exec(const FunctionObject *fo, const QV4::Value *thisObj CppStackFrame frame; frame.originalArguments = argv; frame.originalArgumentsCount = argc; + frame.yield = nullptr; + frame.exceptionHandler = nullptr; Function *function; { @@ -586,10 +589,9 @@ QV4::ReturnedValue VME::interpret(CppStackFrame &frame, const uchar *code) { QV4::Function *function = frame.v4Function; QV4::Value &accumulator = frame.jsFrame->accumulator; - QV4::ReturnedValue acc = Encode::undefined(); + QV4::ReturnedValue acc = accumulator.asReturnedValue(); Value *stack = reinterpret_cast(frame.jsFrame); ExecutionEngine *engine = function->internalClass->engine; - const uchar *exceptionHandler = nullptr; MOTH_JUMP_TABLE; @@ -798,6 +800,24 @@ QV4::ReturnedValue VME::interpret(CppStackFrame &frame, const uchar *code) CHECK_EXCEPTION; MOTH_END_INSTR(LoadIdObject) + MOTH_BEGIN_INSTR(Yield) + frame.yield = code; + return acc; + MOTH_END_INSTR(Yield) + + MOTH_BEGIN_INSTR(Resume) + // check exception, in case the generator was called with throw() or return() + if (engine->hasException) { + // an empty value indicates that the generator was called with return() + if (engine->exceptionValue->asReturnedValue() != Primitive::emptyValue().asReturnedValue()) + goto catchException; + engine->hasException = false; + *engine->exceptionValue = Primitive::undefinedValue(); + } else { + code += offset; + } + MOTH_END_INSTR(Resume) + MOTH_BEGIN_INSTR(CallValue) STORE_IP(); Value func = STACK_VALUE(name); @@ -867,7 +887,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame &frame, const uchar *code) MOTH_END_INSTR(CallContextObjectProperty) MOTH_BEGIN_INSTR(SetExceptionHandler) - exceptionHandler = offset ? code + offset : nullptr; + frame.exceptionHandler = offset ? code + offset : nullptr; MOTH_END_INSTR(SetExceptionHandler) MOTH_BEGIN_INSTR(ThrowException) @@ -1406,10 +1426,10 @@ QV4::ReturnedValue VME::interpret(CppStackFrame &frame, const uchar *code) catchException: Q_ASSERT(engine->hasException); - if (!exceptionHandler) { + if (!frame.exceptionHandler) { acc = Encode::undefined(); return acc; } - code = exceptionHandler; + code = frame.exceptionHandler; } } diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index 9d6f1966e4..c25f3395e2 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -3627,7 +3627,7 @@ MethodDefinition: PropertyName T_LPAREN StrictFormalParameters T_RPAREN Function } break; ./ -MethodDefinition: T_STAR PropertyName T_LPAREN StrictFormalParameters T_RPAREN GeneratorLBrace GeneratorBody GeneratorRBrace; +MethodDefinition: T_STAR PropertyName GeneratorLParen StrictFormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; /. case $rule_number: { AST::FunctionExpression *f = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(7).StatementList); @@ -3683,10 +3683,9 @@ PropertySetParameterList: FormalParameter; } break; ./ -GeneratorLBrace: T_LBRACE; +GeneratorLParen: T_LPAREN; /. case $rule_number: { - ++functionNestingLevel; lexer->enterGeneratorBody(); } break; ./ @@ -3699,7 +3698,7 @@ GeneratorRBrace: T_RBRACE; } break; ./ -GeneratorDeclaration: Function T_STAR BindingIdentifier T_LPAREN FormalParameters T_RPAREN GeneratorLBrace GeneratorBody GeneratorRBrace; +GeneratorDeclaration: Function T_STAR BindingIdentifier GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; /. case $rule_number: { AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(3), sym(5).FormalParameterList, sym(8).StatementList); @@ -3715,7 +3714,7 @@ GeneratorDeclaration: Function T_STAR BindingIdentifier T_LPAREN FormalParameter ./ GeneratorDeclaration_Default: GeneratorDeclaration; -GeneratorDeclaration_Default: Function T_STAR T_LPAREN FormalParameters T_RPAREN GeneratorLBrace GeneratorBody GeneratorRBrace; +GeneratorDeclaration_Default: Function T_STAR GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; /. case $rule_number: { AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(1), sym(4).FormalParameterList, sym(7).StatementList); @@ -3730,7 +3729,7 @@ GeneratorDeclaration_Default: Function T_STAR T_LPAREN FormalParameters T_RPAREN } break; ./ -GeneratorExpression: T_FUNCTION T_STAR BindingIdentifier T_LPAREN FormalParameters T_RPAREN GeneratorLBrace GeneratorBody GeneratorRBrace; +GeneratorExpression: T_FUNCTION T_STAR BindingIdentifier GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; /. case $rule_number: { AST::FunctionExpression *node = new (pool) AST::FunctionExpression(stringRef(3), sym(5).FormalParameterList, sym(8).StatementList); @@ -3746,7 +3745,7 @@ GeneratorExpression: T_FUNCTION T_STAR BindingIdentifier T_LPAREN FormalParamete } break; ./ -GeneratorExpression: T_FUNCTION T_STAR T_LPAREN FormalParameters T_RPAREN GeneratorLBrace GeneratorBody GeneratorRBrace; +GeneratorExpression: T_FUNCTION T_STAR GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; /. case $rule_number: { AST::FunctionExpression *node = new (pool) AST::FunctionExpression(QStringRef(), sym(4).FormalParameterList, sym(7).StatementList); -- cgit v1.2.3