| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQml module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qv4codegen_p.h" |
| #include "qv4util_p.h" |
| |
| #include <QtCore/QCoreApplication> |
| #include <QtCore/QStringList> |
| #include <QtCore/QStack> |
| #include <QtCore/qurl.h> |
| #include <QScopeGuard> |
| #include <private/qqmljsast_p.h> |
| #include <private/qqmljslexer_p.h> |
| #include <private/qqmljsparser_p.h> |
| #include <private/qv4staticvalue_p.h> |
| #include <private/qv4compilercontext_p.h> |
| #include <private/qv4compilercontrolflow_p.h> |
| #include <private/qv4bytecodegenerator_p.h> |
| #include <private/qv4compilerscanfunctions_p.h> |
| #include <private/qv4stringtoarrayindex_p.h> |
| #include <private/qqmljsdiagnosticmessage_p.h> |
| |
| #include <cmath> |
| #include <iostream> |
| |
| static const bool disable_lookups = false; |
| |
| #ifdef CONST |
| #undef CONST |
| #endif |
| |
| QT_USE_NAMESPACE |
| using namespace QV4; |
| using namespace QV4::Compiler; |
| using namespace QQmlJS; |
| using namespace QQmlJS::AST; |
| |
| static inline void setJumpOutLocation(QV4::Moth::BytecodeGenerator *bytecodeGenerator, |
| const Statement *body, const SourceLocation &fallback) |
| { |
| switch (body->kind) { |
| // Statements where we might never execute the last line. |
| // Use the fallback. |
| case Statement::Kind_ConditionalExpression: |
| case Statement::Kind_ForEachStatement: |
| case Statement::Kind_ForStatement: |
| case Statement::Kind_IfStatement: |
| case Statement::Kind_WhileStatement: |
| bytecodeGenerator->setLocation(fallback); |
| break; |
| default: |
| bytecodeGenerator->setLocation(body->lastSourceLocation()); |
| break; |
| } |
| } |
| |
| Codegen::Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict) |
| : _module(nullptr) |
| , _returnAddress(-1) |
| , _context(nullptr) |
| , _labelledStatement(nullptr) |
| , jsUnitGenerator(jsUnitGenerator) |
| , _strictMode(strict) |
| , _fileNameIsUrl(false) |
| { |
| jsUnitGenerator->codeGeneratorName = QStringLiteral("moth"); |
| pushExpr(); |
| } |
| |
| const char *Codegen::s_globalNames[] = { |
| "isNaN", |
| "parseFloat", |
| "String", |
| "EvalError", |
| "URIError", |
| "Math", |
| "encodeURIComponent", |
| "RangeError", |
| "eval", |
| "isFinite", |
| "ReferenceError", |
| "Infinity", |
| "Function", |
| "RegExp", |
| "Number", |
| "parseInt", |
| "Object", |
| "decodeURI", |
| "TypeError", |
| "Boolean", |
| "encodeURI", |
| "NaN", |
| "Error", |
| "decodeURIComponent", |
| "Date", |
| "Array", |
| "Symbol", |
| "escape", |
| "unescape", |
| "SyntaxError", |
| "undefined", |
| "JSON", |
| "ArrayBuffer", |
| "SharedArrayBuffer", |
| "DataView", |
| "Int8Array", |
| "Uint8Array", |
| "Uint8ClampedArray", |
| "Int16Array", |
| "Uint16Array", |
| "Int32Array", |
| "Uint32Array", |
| "Float32Array", |
| "Float64Array", |
| "WeakSet", |
| "Set", |
| "WeakMap", |
| "Map", |
| "Reflect", |
| "Proxy", |
| "Atomics", |
| "Promise", |
| nullptr |
| }; |
| |
| void Codegen::generateFromProgram(const QString &fileName, |
| const QString &finalUrl, |
| const QString &sourceCode, |
| Program *node, |
| Module *module, |
| ContextType contextType) |
| { |
| Q_ASSERT(node); |
| |
| _module = module; |
| _context = nullptr; |
| |
| // ### should be set on the module outside of this method |
| _module->fileName = fileName; |
| _module->finalUrl = finalUrl; |
| |
| if (contextType == ContextType::ScriptImportedByQML) { |
| // the global object is frozen, so we know that members of it are |
| // pointing to the global object. This is important so that references |
| // to Math etc. do not go through the expensive path in the context wrapper |
| // that tries to see whether we have a matching type |
| // |
| // Since this can be called from the loader thread we can't get the list |
| // directly from the engine, so let's hardcode the most important ones here |
| for (const char **g = s_globalNames; *g != nullptr; ++g) |
| m_globalNames << QString::fromLatin1(*g); |
| } |
| |
| ScanFunctions scan(this, sourceCode, contextType); |
| scan(node); |
| |
| if (hasError()) |
| return; |
| |
| defineFunction(QStringLiteral("%entry"), node, nullptr, node->statements); |
| } |
| |
| void Codegen::generateFromModule(const QString &fileName, |
| const QString &finalUrl, |
| const QString &sourceCode, |
| ESModule *node, |
| Module *module) |
| { |
| Q_ASSERT(node); |
| |
| _module = module; |
| _context = nullptr; |
| |
| // ### should be set on the module outside of this method |
| _module->fileName = fileName; |
| _module->finalUrl = finalUrl; |
| |
| ScanFunctions scan(this, sourceCode, ContextType::ESModule); |
| scan(node); |
| |
| if (hasError()) |
| return; |
| |
| { |
| Compiler::Context *moduleContext = _module->contextMap.value(node); |
| for (const auto &entry: moduleContext->exportEntries) { |
| if (entry.moduleRequest.isEmpty()) { |
| // ### check against imported bound names |
| _module->localExportEntries << entry; |
| } else if (entry.importName == QLatin1Char('*')) { |
| _module->starExportEntries << entry; |
| } else { |
| _module->indirectExportEntries << entry; |
| } |
| } |
| _module->importEntries = moduleContext->importEntries; |
| |
| _module->moduleRequests = std::move(moduleContext->moduleRequests); |
| _module->moduleRequests.removeDuplicates(); |
| } |
| |
| std::sort(_module->localExportEntries.begin(), _module->localExportEntries.end(), ExportEntry::lessThan); |
| std::sort(_module->starExportEntries.begin(), _module->starExportEntries.end(), ExportEntry::lessThan); |
| std::sort(_module->indirectExportEntries.begin(), _module->indirectExportEntries.end(), ExportEntry::lessThan); |
| |
| defineFunction(QStringLiteral("%entry"), node, nullptr, node->body); |
| } |
| |
| void Codegen::enterContext(Node *node) |
| { |
| _context = _module->contextMap.value(node); |
| Q_ASSERT(_context); |
| } |
| |
| int Codegen::leaveContext() |
| { |
| Q_ASSERT(_context); |
| int functionIndex = _context->functionIndex; |
| _context = _context->parent; |
| return functionIndex; |
| } |
| |
| Context *Codegen::enterBlock(Node *node) |
| { |
| enterContext(node); |
| return _context; |
| } |
| |
| Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) |
| { |
| if (hasError()) |
| return exprResult(); |
| |
| if (expr.isConstant()) { |
| auto v = StaticValue::fromReturnedValue(expr.constant); |
| if (v.isNumber()) { |
| switch (op) { |
| case Not: |
| return Reference::fromConst(this, Encode(!v.toBoolean())); |
| case UMinus: |
| // This duplicates some of the logic from Runtime::UMinus::call() |
| ReturnedValue r; |
| if (v.isInteger()) { |
| int intVal = v.integerValue(); |
| if (intVal && intVal != std::numeric_limits<int>::min()) |
| r = QV4::Encode(-intVal); |
| else |
| r = QV4::Encode(-double(intVal)); |
| } else if (v.isDouble()) { |
| r = QV4::Encode(-v.doubleValue()); |
| } else { |
| r = QV4::Encode(-v.int_32()); |
| } |
| return Reference::fromConst(this, r); |
| case UPlus: |
| return expr; |
| case Compl: |
| return Reference::fromConst(this, Encode((int)~v.toInt32())); |
| default: |
| break; |
| } |
| } |
| } |
| |
| switch (op) { |
| case UMinus: { |
| expr.loadInAccumulator(); |
| Instruction::UMinus uminus = {}; |
| bytecodeGenerator->addInstruction(uminus); |
| return Reference::fromAccumulator(this); |
| } |
| case UPlus: { |
| expr.loadInAccumulator(); |
| Instruction::UPlus uplus = {}; |
| bytecodeGenerator->addInstruction(uplus); |
| return Reference::fromAccumulator(this); |
| } |
| case Not: { |
| expr.loadInAccumulator(); |
| Instruction::UNot unot; |
| bytecodeGenerator->addInstruction(unot); |
| return Reference::fromAccumulator(this); |
| } |
| case Compl: { |
| expr.loadInAccumulator(); |
| Instruction::UCompl ucompl; |
| bytecodeGenerator->addInstruction(ucompl); |
| return Reference::fromAccumulator(this); |
| } |
| case PostIncrement: |
| if (!exprAccept(nx) || requiresReturnValue) { |
| Reference e = expr.asLValue(); |
| e.loadInAccumulator(); |
| Instruction::UPlus uplus = {}; |
| bytecodeGenerator->addInstruction(uplus); |
| Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); |
| Instruction::Increment inc = {}; |
| bytecodeGenerator->addInstruction(inc); |
| e.storeConsumeAccumulator(); |
| return originalValue; |
| } else { |
| // intentionally fall-through: the result is never used, so it's equivalent to |
| // "expr += 1", which is what a pre-increment does as well. |
| Q_FALLTHROUGH(); |
| } |
| case PreIncrement: { |
| Reference e = expr.asLValue(); |
| e.loadInAccumulator(); |
| Instruction::Increment inc = {}; |
| bytecodeGenerator->addInstruction(inc); |
| if (exprAccept(nx)) |
| return e.storeConsumeAccumulator(); |
| else |
| return e.storeRetainAccumulator(); |
| } |
| case PostDecrement: |
| if (!exprAccept(nx) || requiresReturnValue) { |
| Reference e = expr.asLValue(); |
| e.loadInAccumulator(); |
| Instruction::UPlus uplus = {}; |
| bytecodeGenerator->addInstruction(uplus); |
| Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); |
| Instruction::Decrement dec = {}; |
| bytecodeGenerator->addInstruction(dec); |
| e.storeConsumeAccumulator(); |
| return originalValue; |
| } else { |
| // intentionally fall-through: the result is never used, so it's equivalent to |
| // "expr -= 1", which is what a pre-decrement does as well. |
| Q_FALLTHROUGH(); |
| } |
| case PreDecrement: { |
| Reference e = expr.asLValue(); |
| e.loadInAccumulator(); |
| Instruction::Decrement dec = {}; |
| bytecodeGenerator->addInstruction(dec); |
| if (exprAccept(nx)) |
| return e.storeConsumeAccumulator(); |
| else |
| return e.storeRetainAccumulator(); |
| } |
| } |
| |
| Q_UNREACHABLE(); |
| } |
| |
| void Codegen::addCJump() |
| { |
| const Result &expression = currentExpr(); |
| bytecodeGenerator->addCJumpInstruction(expression.trueBlockFollowsCondition(), |
| expression.iftrue(), expression.iffalse()); |
| } |
| |
| void Codegen::statement(Statement *ast) |
| { |
| RegisterScope scope(this); |
| |
| bytecodeGenerator->setLocation(ast->firstSourceLocation()); |
| |
| VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); |
| qSwap(_volatileMemoryLocations, vLocs); |
| accept(ast); |
| qSwap(_volatileMemoryLocations, vLocs); |
| } |
| |
| void Codegen::statement(ExpressionNode *ast) |
| { |
| if (! ast) { |
| return; |
| } else { |
| RegisterScope scope(this); |
| |
| pushExpr(Result(nx)); |
| VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); |
| qSwap(_volatileMemoryLocations, vLocs); |
| |
| accept(ast); |
| |
| qSwap(_volatileMemoryLocations, vLocs); |
| Reference result = popResult(); |
| |
| if (hasError()) |
| return; |
| if (result.loadTriggersSideEffect()) |
| result.loadInAccumulator(); // triggers side effects |
| } |
| } |
| |
| void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *iftrue, |
| const BytecodeGenerator::Label *iffalse, bool trueBlockFollowsCondition) |
| { |
| if (hasError()) |
| return; |
| |
| if (!ast) |
| return; |
| |
| pushExpr(Result(iftrue, iffalse, trueBlockFollowsCondition)); |
| accept(ast); |
| Result r = popExpr(); |
| |
| if (hasError()) |
| return; |
| |
| if (r.format() == ex) { |
| Q_ASSERT(iftrue == r.iftrue()); |
| Q_ASSERT(iffalse == r.iffalse()); |
| Q_ASSERT(r.result().isValid()); |
| bytecodeGenerator->setLocation(ast->firstSourceLocation()); |
| r.result().loadInAccumulator(); |
| if (r.trueBlockFollowsCondition()) |
| bytecodeGenerator->jumpFalse().link(*r.iffalse()); |
| else |
| bytecodeGenerator->jumpTrue().link(*r.iftrue()); |
| } |
| } |
| |
| void Codegen::program(Program *ast) |
| { |
| if (ast) { |
| statementList(ast->statements); |
| } |
| } |
| |
| enum class CompletionState { |
| Empty, |
| EmptyAbrupt, |
| NonEmpty |
| }; |
| |
| static CompletionState completionState(StatementList *list) |
| { |
| for (StatementList *it = list; it; it = it->next) { |
| if (it->statement->kind == Statement::Kind_BreakStatement || |
| it->statement->kind == Statement::Kind_ContinueStatement) |
| return CompletionState::EmptyAbrupt; |
| if (it->statement->kind == Statement::Kind_EmptyStatement || |
| it->statement->kind == Statement::Kind_VariableDeclaration || |
| it->statement->kind == Statement::Kind_FunctionDeclaration) |
| continue; |
| if (it->statement->kind == Statement::Kind_Block) { |
| CompletionState subState = completionState(static_cast<Block *>(it->statement)->statements); |
| if (subState != CompletionState::Empty) |
| return subState; |
| continue; |
| } |
| return CompletionState::NonEmpty; |
| } |
| return CompletionState::Empty; |
| } |
| |
| static Node *completionStatement(StatementList *list) |
| { |
| Node *completionStatement = nullptr; |
| for (StatementList *it = list; it; it = it->next) { |
| if (it->statement->kind == Statement::Kind_BreakStatement || |
| it->statement->kind == Statement::Kind_ContinueStatement) |
| return completionStatement; |
| if (it->statement->kind == Statement::Kind_ThrowStatement || |
| it->statement->kind == Statement::Kind_ReturnStatement) |
| return it->statement; |
| if (it->statement->kind == Statement::Kind_EmptyStatement || |
| it->statement->kind == Statement::Kind_VariableStatement || |
| it->statement->kind == Statement::Kind_FunctionDeclaration) |
| continue; |
| if (it->statement->kind == Statement::Kind_Block) { |
| CompletionState state = completionState(static_cast<Block *>(it->statement)->statements); |
| switch (state) { |
| case CompletionState::Empty: |
| continue; |
| case CompletionState::EmptyAbrupt: |
| return it->statement; |
| case CompletionState::NonEmpty: |
| break; |
| } |
| } |
| completionStatement = it->statement; |
| } |
| return completionStatement; |
| } |
| |
| void Codegen::statementList(StatementList *ast) |
| { |
| if (!ast) |
| return; |
| |
| bool _requiresReturnValue = requiresReturnValue; |
| // ### the next line is pessimizing a bit too much, as there are many cases, where the complietion from the break |
| // statement will not be used, but it's at least spec compliant |
| if (!controlFlow || !controlFlow->hasLoop()) |
| requiresReturnValue = false; |
| |
| Node *needsCompletion = nullptr; |
| |
| if (_requiresReturnValue && !requiresReturnValue) |
| needsCompletion = completionStatement(ast); |
| |
| if (requiresReturnValue && !needsCompletion && !insideSwitch) { |
| // break or continue is the first real statement, set the return value to undefined |
| Reference::fromConst(this, Encode::undefined()).storeOnStack(_returnAddress); |
| } |
| |
| bool _insideSwitch = insideSwitch; |
| insideSwitch = false; |
| |
| for (StatementList *it = ast; it; it = it->next) { |
| if (it->statement == needsCompletion) |
| requiresReturnValue = true; |
| if (Statement *s = it->statement->statementCast()) |
| statement(s); |
| else |
| statement(static_cast<ExpressionNode *>(it->statement)); |
| if (it->statement == needsCompletion) |
| requiresReturnValue = false; |
| if (it->statement->kind == Statement::Kind_ThrowStatement || |
| it->statement->kind == Statement::Kind_BreakStatement || |
| it->statement->kind == Statement::Kind_ContinueStatement || |
| it->statement->kind == Statement::Kind_ReturnStatement) |
| // any code after those statements is unreachable |
| break; |
| } |
| requiresReturnValue = _requiresReturnValue; |
| insideSwitch = _insideSwitch; |
| } |
| |
| void Codegen::variableDeclaration(PatternElement *ast) |
| { |
| TailCallBlocker blockTailCalls(this); |
| RegisterScope scope(this); |
| |
| if (!ast->initializer) { |
| if (ast->isLexicallyScoped()) { |
| Reference::fromConst(this, Encode::undefined()).loadInAccumulator(); |
| Reference varToStore = targetForPatternElement(ast); |
| varToStore.storeConsumeAccumulator(); |
| } |
| return; |
| } |
| initializeAndDestructureBindingElement(ast, Reference(), /*isDefinition*/ true); |
| } |
| |
| void Codegen::variableDeclarationList(VariableDeclarationList *ast) |
| { |
| for (VariableDeclarationList *it = ast; it; it = it->next) { |
| variableDeclaration(it->declaration); |
| } |
| } |
| |
| Codegen::Reference Codegen::targetForPatternElement(AST::PatternElement *p) |
| { |
| if (!p->bindingIdentifier.isNull()) |
| return referenceForName(p->bindingIdentifier.toString(), true, p->firstSourceLocation()); |
| if (!p->bindingTarget || p->destructuringPattern()) |
| return Codegen::Reference::fromStackSlot(this); |
| Reference lhs = expression(p->bindingTarget); |
| if (hasError()) |
| return lhs; |
| if (!lhs.isLValue()) { |
| throwReferenceError(p->bindingTarget->firstSourceLocation(), QStringLiteral("Binding target is not a reference.")); |
| return lhs; |
| } |
| lhs = lhs.asLValue(); |
| return lhs; |
| } |
| |
| void Codegen::initializeAndDestructureBindingElement(AST::PatternElement *e, const Reference &base, bool isDefinition) |
| { |
| Q_ASSERT(e->type == AST::PatternElement::Binding || e->type == AST::PatternElement::RestElement); |
| RegisterScope scope(this); |
| Reference baseRef = (base.isAccumulator()) ? base.storeOnStack() : base; |
| Reference varToStore = targetForPatternElement(e); |
| if (isDefinition) |
| varToStore.isReferenceToConst = false; |
| if (hasError()) |
| return; |
| |
| accept(e->typeAnnotation); |
| |
| if (e->initializer) { |
| if (!baseRef.isValid()) { |
| // assignment |
| Reference expr = expression(e->initializer); |
| if (hasError()) |
| return; |
| expr.loadInAccumulator(); |
| varToStore.storeConsumeAccumulator(); |
| } else if (baseRef == varToStore) { |
| baseRef.loadInAccumulator(); |
| BytecodeGenerator::Jump jump = bytecodeGenerator->jumpNotUndefined(); |
| Reference expr = expression(e->initializer); |
| if (hasError()) { |
| jump.link(); |
| return; |
| } |
| expr.loadInAccumulator(); |
| varToStore.storeConsumeAccumulator(); |
| jump.link(); |
| } else { |
| baseRef.loadInAccumulator(); |
| BytecodeGenerator::Jump jump = bytecodeGenerator->jumpNotUndefined(); |
| Reference expr = expression(e->initializer); |
| if (hasError()) { |
| jump.link(); |
| return; |
| } |
| expr.loadInAccumulator(); |
| jump.link(); |
| varToStore.storeConsumeAccumulator(); |
| } |
| } else if (baseRef != varToStore && baseRef.isValid()) { |
| baseRef.loadInAccumulator(); |
| varToStore.storeConsumeAccumulator(); |
| } |
| Pattern *p = e->destructuringPattern(); |
| if (!p) |
| return; |
| |
| if (!varToStore.isStackSlot()) |
| varToStore = varToStore.storeOnStack(); |
| if (PatternElementList *l = e->elementList()) { |
| destructureElementList(varToStore, l, isDefinition); |
| } else if (PatternPropertyList *p = e->propertyList()) { |
| destructurePropertyList(varToStore, p, isDefinition); |
| } else if (e->bindingTarget) { |
| // empty binding pattern. For spec compatibility, try to coerce the argument to an object |
| varToStore.loadInAccumulator(); |
| Instruction::ToObject toObject; |
| bytecodeGenerator->addInstruction(toObject); |
| return; |
| } |
| } |
| |
| Codegen::Reference Codegen::referenceForPropertyName(const Codegen::Reference &object, AST::PropertyName *name) |
| { |
| AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(name); |
| Reference property; |
| if (cname) { |
| Reference computedName = expression(cname->expression); |
| if (hasError()) |
| return Reference(); |
| computedName = computedName.storeOnStack(); |
| property = Reference::fromSubscript(object, computedName).asLValue(); |
| } else { |
| QString propertyName = name->asString(); |
| property = Reference::fromMember(object, propertyName); |
| } |
| return property; |
| } |
| |
| void Codegen::destructurePropertyList(const Codegen::Reference &object, PatternPropertyList *bindingList, bool isDefinition) |
| { |
| RegisterScope scope(this); |
| |
| object.loadInAccumulator(); |
| Instruction::ThrowOnNullOrUndefined t; |
| bytecodeGenerator->addInstruction(t); |
| |
| for (PatternPropertyList *it = bindingList; it; it = it->next) { |
| PatternProperty *p = it->property; |
| RegisterScope scope(this); |
| Reference property = referenceForPropertyName(object, p->name); |
| if (hasError()) |
| return; |
| initializeAndDestructureBindingElement(p, property, isDefinition); |
| if (hasError()) |
| return; |
| } |
| } |
| |
| void Codegen::destructureElementList(const Codegen::Reference &array, PatternElementList *bindingList, bool isDefinition) |
| { |
| RegisterScope scope(this); |
| |
| Reference iterator = Reference::fromStackSlot(this); |
| Reference iteratorValue = Reference::fromStackSlot(this); |
| Reference iteratorDone = Reference::fromStackSlot(this); |
| Reference::storeConstOnStack(this, Encode(false), iteratorDone.stackSlot()); |
| |
| array.loadInAccumulator(); |
| Instruction::GetIterator iteratorObjInstr; |
| iteratorObjInstr.iterator = static_cast<int>(AST::ForEachType::Of); |
| bytecodeGenerator->addInstruction(iteratorObjInstr); |
| iterator.storeConsumeAccumulator(); |
| |
| { |
| auto cleanup = [this, iterator, iteratorDone]() { |
| iterator.loadInAccumulator(); |
| Instruction::IteratorClose close; |
| close.done = iteratorDone.stackSlot(); |
| bytecodeGenerator->addInstruction(close); |
| }; |
| |
| ControlFlowUnwindCleanup flow(this, cleanup); |
| |
| for (PatternElementList *p = bindingList; p; p = p->next) { |
| PatternElement *e = p->element; |
| for (Elision *elision = p->elision; elision; elision = elision->next) { |
| iterator.loadInAccumulator(); |
| Instruction::IteratorNext next; |
| next.value = iteratorValue.stackSlot(); |
| next.done = iteratorDone.stackSlot(); |
| bytecodeGenerator->addInstruction(next); |
| } |
| |
| if (!e) |
| continue; |
| |
| RegisterScope scope(this); |
| iterator.loadInAccumulator(); |
| |
| if (e->type == PatternElement::RestElement) { |
| Reference::fromConst(this, Encode(true)).storeOnStack(iteratorDone.stackSlot()); |
| bytecodeGenerator->addInstruction(Instruction::DestructureRestElement()); |
| initializeAndDestructureBindingElement(e, Reference::fromAccumulator(this), isDefinition); |
| } else { |
| Instruction::IteratorNext next; |
| next.value = iteratorValue.stackSlot(); |
| next.done = iteratorDone.stackSlot(); |
| bytecodeGenerator->addInstruction(next); |
| initializeAndDestructureBindingElement(e, iteratorValue, isDefinition); |
| if (hasError()) |
| return; |
| } |
| } |
| } |
| } |
| |
| void Codegen::destructurePattern(Pattern *p, const Reference &rhs) |
| { |
| RegisterScope scope(this); |
| if (auto *o = AST::cast<ObjectPattern *>(p)) |
| destructurePropertyList(rhs, o->properties); |
| else if (auto *a = AST::cast<ArrayPattern *>(p)) |
| destructureElementList(rhs, a->elements); |
| else |
| Q_UNREACHABLE(); |
| } |
| |
| |
| bool Codegen::visit(ArgumentList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(CaseBlock *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(CaseClause *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(CaseClauses *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(Catch *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(DefaultClause *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(Elision *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(Finally *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(FormalParameterList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(Program *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(PatternElement *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(PatternElementList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(PatternProperty *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(PatternPropertyList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(ExportDeclaration *ast) |
| { |
| if (!ast->exportDefault) |
| return true; |
| |
| TailCallBlocker blockTailCalls(this); |
| Reference exportedValue; |
| |
| if (auto *fdecl = AST::cast<FunctionDeclaration*>(ast->variableStatementOrDeclaration)) { |
| pushExpr(); |
| visit(static_cast<FunctionExpression*>(fdecl)); |
| exportedValue = popResult(); |
| } else if (auto *classDecl = AST::cast<ClassDeclaration*>(ast->variableStatementOrDeclaration)) { |
| pushExpr(); |
| visit(static_cast<ClassExpression*>(classDecl)); |
| exportedValue = popResult(); |
| } else if (ExpressionNode *expr = ast->variableStatementOrDeclaration->expressionCast()) { |
| exportedValue = expression(expr); |
| } |
| |
| exportedValue.loadInAccumulator(); |
| |
| const int defaultExportIndex = _context->locals.indexOf(_context->localNameForDefaultExport); |
| Q_ASSERT(defaultExportIndex != -1); |
| Reference defaultExportSlot = Reference::fromScopedLocal(this, defaultExportIndex, /*scope*/0); |
| defaultExportSlot.storeConsumeAccumulator(); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(TypeAnnotation *ast) |
| { |
| throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Type annotations are not supported (yet).")); |
| return false; |
| } |
| |
| bool Codegen::visit(StatementList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiArrayMemberList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiImport *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiHeaderItemList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiPragma *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiObjectInitializer *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiObjectMemberList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiParameterList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiProgram *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiQualifiedId *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(VariableDeclarationList *) |
| { |
| Q_UNREACHABLE(); |
| return false; |
| } |
| |
| bool Codegen::visit(ClassExpression *ast) |
| { |
| TailCallBlocker blockTailCalls(this); |
| |
| Compiler::Class jsClass; |
| jsClass.nameIndex = registerString(ast->name.toString()); |
| |
| ClassElementList *constructor = nullptr; |
| int nComputedNames = 0; |
| int nStaticComputedNames = 0; |
| |
| RegisterScope scope(this); |
| ControlFlowBlock controlFlow(this, ast); |
| |
| for (auto *member = ast->elements; member; member = member->next) { |
| PatternProperty *p = member->property; |
| FunctionExpression *f = p->initializer->asFunctionDefinition(); |
| Q_ASSERT(f); |
| AST::ComputedPropertyName *cname = AST::cast<ComputedPropertyName *>(p->name); |
| if (cname) { |
| ++nComputedNames; |
| if (member->isStatic) |
| ++nStaticComputedNames; |
| } |
| QString name = p->name->asString(); |
| uint nameIndex = cname ? UINT_MAX : registerString(name); |
| Compiler::Class::Method::Type type = Compiler::Class::Method::Regular; |
| if (p->type == PatternProperty::Getter) |
| type = Compiler::Class::Method::Getter; |
| else if (p->type == PatternProperty::Setter) |
| type = Compiler::Class::Method::Setter; |
| Compiler::Class::Method m{ nameIndex, type, static_cast<uint>(defineFunction(name, f, f->formals, f->body)) }; |
| |
| if (member->isStatic) { |
| if (name == QStringLiteral("prototype")) { |
| throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Cannot declare a static method named 'prototype'.")); |
| return false; |
| } |
| jsClass.staticMethods << m; |
| } else { |
| if (name == QStringLiteral("constructor")) { |
| if (constructor) { |
| throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Cannot declare a multiple constructors in a class.")); |
| return false; |
| } |
| if (m.type != Compiler::Class::Method::Regular) { |
| throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Cannot declare a getter or setter named 'constructor'.")); |
| return false; |
| } |
| constructor = member; |
| jsClass.constructorIndex = m.functionIndex; |
| continue; |
| } |
| |
| jsClass.methods << m; |
| } |
| } |
| |
| int classIndex = _module->classes.size(); |
| _module->classes.append(jsClass); |
| |
| Reference heritage = Reference::fromStackSlot(this); |
| if (ast->heritage) { |
| bytecodeGenerator->setLocation(ast->heritage->firstSourceLocation()); |
| Reference r = expression(ast->heritage); |
| if (hasError()) |
| return false; |
| r.storeOnStack(heritage.stackSlot()); |
| } else { |
| Reference::fromConst(this, StaticValue::emptyValue().asReturnedValue()).loadInAccumulator(); |
| heritage.storeConsumeAccumulator(); |
| } |
| |
| int computedNames = nComputedNames ? bytecodeGenerator->newRegisterArray(nComputedNames) : 0; |
| int currentStaticName = computedNames; |
| int currentNonStaticName = computedNames + nStaticComputedNames; |
| |
| for (auto *member = ast->elements; member; member = member->next) { |
| AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(member->property->name); |
| if (!cname) |
| continue; |
| RegisterScope scope(this); |
| bytecodeGenerator->setLocation(cname->firstSourceLocation()); |
| Reference computedName = expression(cname->expression); |
| if (hasError()) |
| return false; |
| computedName.storeOnStack(member->isStatic ? currentStaticName++ : currentNonStaticName++); |
| } |
| |
| Instruction::CreateClass createClass; |
| createClass.classIndex = classIndex; |
| createClass.heritage = heritage.stackSlot(); |
| createClass.computedNames = computedNames; |
| |
| bytecodeGenerator->addInstruction(createClass); |
| |
| if (!ast->name.isEmpty()) { |
| Reference ctor = referenceForName(ast->name.toString(), true); |
| ctor.isReferenceToConst = false; // this is the definition |
| (void) ctor.storeRetainAccumulator(); |
| } |
| |
| setExprResult(Reference::fromAccumulator(this)); |
| return false; |
| } |
| |
| bool Codegen::visit(ClassDeclaration *ast) |
| { |
| TailCallBlocker blockTailCalls(this); |
| Reference outerVar = referenceForName(ast->name.toString(), true); |
| visit(static_cast<ClassExpression *>(ast)); |
| (void) outerVar.storeRetainAccumulator(); |
| return false; |
| } |
| |
| bool Codegen::visit(Expression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| statement(ast->left); |
| blockTailCalls.unblock(); |
| clearExprResultName(); // The name only holds for the left part |
| accept(ast->right); |
| return false; |
| } |
| |
| bool Codegen::visit(ArrayPattern *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| |
| PatternElementList *it = ast->elements; |
| |
| int argc = 0; |
| { |
| RegisterScope scope(this); |
| |
| int args = -1; |
| auto push = [this, &argc, &args](AST::ExpressionNode *arg) { |
| int temp = bytecodeGenerator->newRegister(); |
| if (args == -1) |
| args = temp; |
| if (!arg) { |
| auto c = Reference::fromConst(this, StaticValue::emptyValue().asReturnedValue()); |
| (void) c.storeOnStack(temp); |
| } else { |
| RegisterScope scope(this); |
| Reference r = expression(arg); |
| if (hasError()) |
| return; |
| (void) r.storeOnStack(temp); |
| } |
| ++argc; |
| }; |
| |
| for (; it; it = it->next) { |
| PatternElement *e = it->element; |
| if (e && e->type == PatternElement::SpreadElement) |
| break; |
| for (Elision *elision = it->elision; elision; elision = elision->next) |
| push(nullptr); |
| |
| if (!e) |
| continue; |
| |
| push(e->initializer); |
| if (hasError()) |
| return false; |
| } |
| |
| if (args == -1) { |
| Q_ASSERT(argc == 0); |
| args = 0; |
| } |
| |
| Instruction::DefineArray call; |
| call.argc = argc; |
| call.args = Moth::StackSlot::createRegister(args); |
| bytecodeGenerator->addInstruction(call); |
| } |
| |
| if (!it) { |
| setExprResult(Reference::fromAccumulator(this)); |
| return false; |
| } |
| Q_ASSERT(it->element && it->element->type == PatternElement::SpreadElement); |
| |
| RegisterScope scope(this); |
| Reference array = Reference::fromStackSlot(this); |
| array.storeConsumeAccumulator(); |
| Reference index = Reference::storeConstOnStack(this, Encode(argc)); |
| |
| auto pushAccumulator = [&]() { |
| Reference slot = Reference::fromSubscript(array, index); |
| slot.storeConsumeAccumulator(); |
| |
| index.loadInAccumulator(); |
| Instruction::Increment inc = {}; |
| bytecodeGenerator->addInstruction(inc); |
| index.storeConsumeAccumulator(); |
| }; |
| |
| while (it) { |
| for (Elision *elision = it->elision; elision; elision = elision->next) { |
| Reference::fromConst( |
| this, StaticValue::emptyValue().asReturnedValue()).loadInAccumulator(); |
| pushAccumulator(); |
| } |
| |
| if (!it->element) { |
| it = it->next; |
| continue; |
| } |
| |
| // handle spread element |
| if (it->element->type == PatternElement::SpreadElement) { |
| RegisterScope scope(this); |
| |
| Reference iterator = Reference::fromStackSlot(this); |
| Reference iteratorDone = Reference::fromConst(this, Encode(false)).storeOnStack(); |
| Reference lhsValue = Reference::fromStackSlot(this); |
| |
| // There should be a temporal block, so that variables declared in lhs shadow outside vars. |
| // This block should define a temporal dead zone for those variables, which is not yet implemented. |
| { |
| RegisterScope innerScope(this); |
| Reference expr = expression(it->element->initializer); |
| if (hasError()) |
| return false; |
| |
| expr.loadInAccumulator(); |
| Instruction::GetIterator iteratorObjInstr; |
| iteratorObjInstr.iterator = static_cast<int>(AST::ForEachType::Of); |
| bytecodeGenerator->addInstruction(iteratorObjInstr); |
| iterator.storeConsumeAccumulator(); |
| } |
| |
| BytecodeGenerator::Label in = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); |
| |
| { |
| auto cleanup = [this, iterator, iteratorDone]() { |
| iterator.loadInAccumulator(); |
| Instruction::IteratorClose close; |
| close.done = iteratorDone.stackSlot(); |
| bytecodeGenerator->addInstruction(close); |
| }; |
| ControlFlowLoop flow(this, &end, &in, cleanup); |
| |
| in.link(); |
| bytecodeGenerator->addLoopStart(in); |
| iterator.loadInAccumulator(); |
| Instruction::IteratorNext next; |
| next.value = lhsValue.stackSlot(); |
| next.done = iteratorDone.stackSlot(); |
| bytecodeGenerator->addInstruction(next); |
| bytecodeGenerator->addJumpInstruction(Instruction::JumpTrue()).link(end); |
| |
| lhsValue.loadInAccumulator(); |
| pushAccumulator(); |
| |
| bytecodeGenerator->checkException(); |
| bytecodeGenerator->jump().link(in); |
| end.link(); |
| } |
| } else { |
| RegisterScope innerScope(this); |
| Reference expr = expression(it->element->initializer); |
| if (hasError()) |
| return false; |
| |
| expr.loadInAccumulator(); |
| pushAccumulator(); |
| } |
| |
| it = it->next; |
| } |
| |
| array.loadInAccumulator(); |
| setExprResult(Reference::fromAccumulator(this)); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(ArrayMemberExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| Reference base = expression(ast->base); |
| if (hasError()) |
| return false; |
| if (base.isSuper()) { |
| Reference index = expression(ast->expression).storeOnStack(); |
| setExprResult(Reference::fromSuperProperty(index)); |
| return false; |
| } |
| base = base.storeOnStack(); |
| if (hasError()) |
| return false; |
| if (AST::StringLiteral *str = AST::cast<AST::StringLiteral *>(ast->expression)) { |
| QString s = str->value.toString(); |
| uint arrayIndex = stringToArrayIndex(s); |
| if (arrayIndex == UINT_MAX) { |
| setExprResult(Reference::fromMember(base, str->value.toString())); |
| return false; |
| } |
| Reference index = Reference::fromConst(this, QV4::Encode(arrayIndex)); |
| setExprResult(Reference::fromSubscript(base, index)); |
| return false; |
| } |
| Reference index = expression(ast->expression); |
| if (hasError()) |
| return false; |
| setExprResult(Reference::fromSubscript(base, index)); |
| return false; |
| } |
| |
| static QSOperator::Op baseOp(int op) |
| { |
| switch ((QSOperator::Op) op) { |
| case QSOperator::InplaceAnd: return QSOperator::BitAnd; |
| case QSOperator::InplaceSub: return QSOperator::Sub; |
| case QSOperator::InplaceDiv: return QSOperator::Div; |
| case QSOperator::InplaceAdd: return QSOperator::Add; |
| case QSOperator::InplaceLeftShift: return QSOperator::LShift; |
| case QSOperator::InplaceMod: return QSOperator::Mod; |
| case QSOperator::InplaceExp: return QSOperator::Exp; |
| case QSOperator::InplaceMul: return QSOperator::Mul; |
| case QSOperator::InplaceOr: return QSOperator::BitOr; |
| case QSOperator::InplaceRightShift: return QSOperator::RShift; |
| case QSOperator::InplaceURightShift: return QSOperator::URShift; |
| case QSOperator::InplaceXor: return QSOperator::BitXor; |
| default: return QSOperator::Invalid; |
| } |
| } |
| |
| bool Codegen::visit(BinaryExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| |
| if (ast->op == QSOperator::And) { |
| if (exprAccept(cx)) { |
| auto iftrue = bytecodeGenerator->newLabel(); |
| condition(ast->left, &iftrue, currentExpr().iffalse(), true); |
| iftrue.link(); |
| blockTailCalls.unblock(); |
| const Result &expr = currentExpr(); |
| condition(ast->right, expr.iftrue(), expr.iffalse(), expr.trueBlockFollowsCondition()); |
| } else { |
| auto iftrue = bytecodeGenerator->newLabel(); |
| auto endif = bytecodeGenerator->newLabel(); |
| |
| Reference left = expression(ast->left); |
| if (hasError()) |
| return false; |
| left.loadInAccumulator(); |
| |
| bytecodeGenerator->setLocation(ast->operatorToken); |
| bytecodeGenerator->jumpFalse().link(endif); |
| iftrue.link(); |
| |
| blockTailCalls.unblock(); |
| Reference right = expression(ast->right); |
| if (hasError()) |
| return false; |
| right.loadInAccumulator(); |
| |
| endif.link(); |
| |
| setExprResult(Reference::fromAccumulator(this)); |
| } |
| return false; |
| } else if (ast->op == QSOperator::Or) { |
| if (exprAccept(cx)) { |
| auto iffalse = bytecodeGenerator->newLabel(); |
| condition(ast->left, currentExpr().iftrue(), &iffalse, false); |
| iffalse.link(); |
| const Result &expr = currentExpr(); |
| condition(ast->right, expr.iftrue(), expr.iffalse(), expr.trueBlockFollowsCondition()); |
| } else { |
| auto iffalse = bytecodeGenerator->newLabel(); |
| auto endif = bytecodeGenerator->newLabel(); |
| |
| Reference left = expression(ast->left); |
| if (hasError()) |
| return false; |
| left.loadInAccumulator(); |
| |
| bytecodeGenerator->setLocation(ast->operatorToken); |
| bytecodeGenerator->jumpTrue().link(endif); |
| iffalse.link(); |
| |
| blockTailCalls.unblock(); |
| Reference right = expression(ast->right); |
| if (hasError()) |
| return false; |
| right.loadInAccumulator(); |
| |
| endif.link(); |
| |
| setExprResult(Reference::fromAccumulator(this)); |
| } |
| return false; |
| } else if (ast->op == QSOperator::Coalesce) { |
| |
| Reference left = expression(ast->left); |
| if (hasError()) |
| return false; |
| |
| BytecodeGenerator::Label iftrue = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label iffalse = bytecodeGenerator->newLabel(); |
| |
| Instruction::CmpNeNull cmp; |
| |
| left = left.storeOnStack(); |
| left.loadInAccumulator(); |
| bytecodeGenerator->addInstruction(cmp); |
| |
| bytecodeGenerator->jumpTrue().link(iftrue); |
| bytecodeGenerator->jumpFalse().link(iffalse); |
| |
| blockTailCalls.unblock(); |
| |
| iftrue.link(); |
| |
| left.loadInAccumulator(); |
| |
| BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump(); |
| |
| iffalse.link(); |
| |
| Reference right = expression(ast->right); |
| right.loadInAccumulator(); |
| jump_endif.link(); |
| setExprResult(Reference::fromAccumulator(this)); |
| |
| return false; |
| } else if (ast->op == QSOperator::Assign) { |
| if (AST::Pattern *p = ast->left->patternCast()) { |
| RegisterScope scope(this); |
| Reference right = expression(ast->right); |
| if (hasError()) |
| return false; |
| right = right.storeOnStack(); |
| destructurePattern(p, right); |
| if (!exprAccept(nx)) { |
| right.loadInAccumulator(); |
| setExprResult(Reference::fromAccumulator(this)); |
| } |
| return false; |
| } |
| Reference left = expression(ast->left); |
| if (hasError()) |
| return false; |
| |
| if (!left.isLValue()) { |
| throwReferenceError(ast->operatorToken, QStringLiteral("left-hand side of assignment operator is not an lvalue")); |
| return false; |
| } |
| left = left.asLValue(); |
| if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation())) |
| return false; |
| blockTailCalls.unblock(); |
| Reference r = expression(ast->right); |
| if (hasError()) |
| return false; |
| r.loadInAccumulator(); |
| if (exprAccept(nx)) |
| setExprResult(left.storeConsumeAccumulator()); |
| else |
| setExprResult(left.storeRetainAccumulator()); |
| return false; |
| } |
| |
| Reference left = expression(ast->left); |
| if (hasError()) |
| return false; |
| |
| switch (ast->op) { |
| case QSOperator::Or: |
| case QSOperator::And: |
| case QSOperator::Assign: |
| Q_UNREACHABLE(); // handled separately above |
| break; |
| |
| case QSOperator::InplaceAnd: |
| case QSOperator::InplaceSub: |
| case QSOperator::InplaceDiv: |
| case QSOperator::InplaceAdd: |
| case QSOperator::InplaceLeftShift: |
| case QSOperator::InplaceMod: |
| case QSOperator::InplaceExp: |
| case QSOperator::InplaceMul: |
| case QSOperator::InplaceOr: |
| case QSOperator::InplaceRightShift: |
| case QSOperator::InplaceURightShift: |
| case QSOperator::InplaceXor: { |
| if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation())) |
| return false; |
| |
| if (!left.isLValue()) { |
| throwSyntaxError(ast->operatorToken, QStringLiteral("left-hand side of inplace operator is not an lvalue")); |
| return false; |
| } |
| left = left.asLValue(); |
| |
| Reference tempLeft = left.storeOnStack(); |
| Reference right = expression(ast->right); |
| |
| if (hasError()) |
| return false; |
| |
| binopHelper(baseOp(ast->op), tempLeft, right).loadInAccumulator(); |
| setExprResult(left.storeRetainAccumulator()); |
| |
| break; |
| } |
| |
| case QSOperator::BitAnd: |
| case QSOperator::BitOr: |
| case QSOperator::BitXor: |
| if (left.isConstant()) { |
| Reference right = expression(ast->right); |
| if (hasError()) |
| return false; |
| setExprResult(binopHelper(static_cast<QSOperator::Op>(ast->op), right, left)); |
| break; |
| } |
| // intentional fall-through! |
| case QSOperator::In: |
| case QSOperator::InstanceOf: |
| case QSOperator::Equal: |
| case QSOperator::NotEqual: |
| case QSOperator::Ge: |
| case QSOperator::Gt: |
| case QSOperator::Le: |
| case QSOperator::Lt: |
| case QSOperator::StrictEqual: |
| case QSOperator::StrictNotEqual: |
| case QSOperator::Add: |
| case QSOperator::Div: |
| case QSOperator::Exp: |
| case QSOperator::Mod: |
| case QSOperator::Mul: |
| case QSOperator::Sub: |
| case QSOperator::LShift: |
| case QSOperator::RShift: |
| case QSOperator::URShift: { |
| Reference right; |
| if (AST::NumericLiteral *rhs = AST::cast<AST::NumericLiteral *>(ast->right)) { |
| visit(rhs); |
| right = exprResult(); |
| } else { |
| left = left.storeOnStack(); // force any loads of the lhs, so the rhs won't clobber it |
| right = expression(ast->right); |
| } |
| if (hasError()) |
| return false; |
| |
| setExprResult(binopHelper(static_cast<QSOperator::Op>(ast->op), left, right)); |
| |
| break; |
| } |
| case QSOperator::As: |
| setExprResult(left); |
| break; |
| } // switch |
| |
| return false; |
| } |
| |
| Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Reference &right) |
| { |
| switch (oper) { |
| case QSOperator::Add: { |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| Instruction::Add add; |
| add.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(add); |
| break; |
| } |
| case QSOperator::Sub: { |
| if (right.isConstant() && right.constant == Encode(int(1))) { |
| left.loadInAccumulator(); |
| Instruction::Decrement dec = {}; |
| bytecodeGenerator->addInstruction(dec); |
| } else { |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| Instruction::Sub sub; |
| sub.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(sub); |
| } |
| break; |
| } |
| case QSOperator::Exp: { |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| Instruction::Exp exp; |
| exp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(exp); |
| break; |
| } |
| case QSOperator::Mul: { |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| Instruction::Mul mul; |
| mul.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(mul); |
| break; |
| } |
| case QSOperator::Div: { |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| Instruction::Div div; |
| div.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(div); |
| break; |
| } |
| case QSOperator::Mod: { |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| Instruction::Mod mod; |
| mod.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(mod); |
| break; |
| } |
| case QSOperator::BitAnd: |
| if (right.isConstant()) { |
| int rightAsInt = StaticValue::fromReturnedValue(right.constant).toInt32(); |
| if (left.isConstant()) { |
| int result = StaticValue::fromReturnedValue(left.constant).toInt32() & rightAsInt; |
| return Reference::fromConst(this, Encode(result)); |
| } |
| left.loadInAccumulator(); |
| Instruction::BitAndConst bitAnd; |
| bitAnd.rhs = rightAsInt; |
| bytecodeGenerator->addInstruction(bitAnd); |
| } else { |
| right.loadInAccumulator(); |
| Instruction::BitAnd bitAnd; |
| bitAnd.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(bitAnd); |
| } |
| break; |
| case QSOperator::BitOr: |
| if (right.isConstant()) { |
| int rightAsInt = StaticValue::fromReturnedValue(right.constant).toInt32(); |
| if (left.isConstant()) { |
| int result = StaticValue::fromReturnedValue(left.constant).toInt32() | rightAsInt; |
| return Reference::fromConst(this, Encode(result)); |
| } |
| left.loadInAccumulator(); |
| Instruction::BitOrConst bitOr; |
| bitOr.rhs = rightAsInt; |
| bytecodeGenerator->addInstruction(bitOr); |
| } else { |
| right.loadInAccumulator(); |
| Instruction::BitOr bitOr; |
| bitOr.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(bitOr); |
| } |
| break; |
| case QSOperator::BitXor: |
| if (right.isConstant()) { |
| int rightAsInt = StaticValue::fromReturnedValue(right.constant).toInt32(); |
| if (left.isConstant()) { |
| int result = StaticValue::fromReturnedValue(left.constant).toInt32() ^ rightAsInt; |
| return Reference::fromConst(this, Encode(result)); |
| } |
| left.loadInAccumulator(); |
| Instruction::BitXorConst bitXor; |
| bitXor.rhs = rightAsInt; |
| bytecodeGenerator->addInstruction(bitXor); |
| } else { |
| right.loadInAccumulator(); |
| Instruction::BitXor bitXor; |
| bitXor.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(bitXor); |
| } |
| break; |
| case QSOperator::URShift: |
| if (right.isConstant()) { |
| left.loadInAccumulator(); |
| Instruction::UShrConst ushr; |
| ushr.rhs = StaticValue::fromReturnedValue(right.constant).toInt32() & 0x1f; |
| bytecodeGenerator->addInstruction(ushr); |
| } else { |
| right.loadInAccumulator(); |
| Instruction::UShr ushr; |
| ushr.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(ushr); |
| } |
| break; |
| case QSOperator::RShift: |
| if (right.isConstant()) { |
| left.loadInAccumulator(); |
| Instruction::ShrConst shr; |
| shr.rhs = StaticValue::fromReturnedValue(right.constant).toInt32() & 0x1f; |
| bytecodeGenerator->addInstruction(shr); |
| } else { |
| right.loadInAccumulator(); |
| Instruction::Shr shr; |
| shr.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(shr); |
| } |
| break; |
| case QSOperator::LShift: |
| if (right.isConstant()) { |
| left.loadInAccumulator(); |
| Instruction::ShlConst shl; |
| shl.rhs = StaticValue::fromReturnedValue(right.constant).toInt32() & 0x1f; |
| bytecodeGenerator->addInstruction(shl); |
| } else { |
| right.loadInAccumulator(); |
| Instruction::Shl shl; |
| shl.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(shl); |
| } |
| break; |
| case QSOperator::InstanceOf: { |
| Instruction::CmpInstanceOf binop; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| binop.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(binop); |
| break; |
| } |
| case QSOperator::In: { |
| Instruction::CmpIn binop; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| binop.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(binop); |
| break; |
| } |
| case QSOperator::StrictEqual: { |
| if (exprAccept(cx)) |
| return jumpBinop(oper, left, right); |
| |
| Instruction::CmpStrictEqual cmp; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| break; |
| } |
| case QSOperator::StrictNotEqual: { |
| if (exprAccept(cx)) |
| return jumpBinop(oper, left, right); |
| |
| Instruction::CmpStrictNotEqual cmp; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| break; |
| } |
| case QSOperator::Equal: { |
| if (exprAccept(cx)) |
| return jumpBinop(oper, left, right); |
| |
| Instruction::CmpEq cmp; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| break; |
| } |
| case QSOperator::NotEqual: { |
| if (exprAccept(cx)) |
| return jumpBinop(oper, left, right); |
| |
| Instruction::CmpNe cmp; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| break; |
| } |
| case QSOperator::Gt: { |
| if (exprAccept(cx)) |
| return jumpBinop(oper, left, right); |
| |
| Instruction::CmpGt cmp; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| break; |
| } |
| case QSOperator::Ge: { |
| if (exprAccept(cx)) |
| return jumpBinop(oper, left, right); |
| |
| Instruction::CmpGe cmp; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| break; |
| } |
| case QSOperator::Lt: { |
| if (exprAccept(cx)) |
| return jumpBinop(oper, left, right); |
| |
| Instruction::CmpLt cmp; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| break; |
| } |
| case QSOperator::Le: |
| if (exprAccept(cx)) |
| return jumpBinop(oper, left, right); |
| |
| Instruction::CmpLe cmp; |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| break; |
| default: |
| Q_UNREACHABLE(); |
| } |
| |
| return Reference::fromAccumulator(this); |
| } |
| |
| Codegen::Reference Codegen::jumpBinop(QSOperator::Op oper, Reference &left, Reference &right) |
| { |
| // See if we can generate specialized comparison instructions: |
| if (oper == QSOperator::Equal || oper == QSOperator::NotEqual) { |
| // Because == and != are reflexive, we can do the following: |
| if (left.isConstant() && !right.isConstant()) |
| qSwap(left, right); // null==a -> a==null |
| |
| if (right.isConstant()) { |
| StaticValue c = StaticValue::fromReturnedValue(right.constant); |
| if (c.isNull() || c.isUndefined()) { |
| left.loadInAccumulator(); |
| if (oper == QSOperator::Equal) { |
| Instruction::CmpEqNull cmp; |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| return Reference(); |
| } else if (oper == QSOperator::NotEqual) { |
| Instruction::CmpNeNull cmp; |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| return Reference(); |
| } |
| } else if (c.isInt32()) { |
| left.loadInAccumulator(); |
| if (oper == QSOperator::Equal) { |
| Instruction::CmpEqInt cmp; |
| cmp.lhs = c.int_32(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| return Reference(); |
| } else if (oper == QSOperator::NotEqual) { |
| Instruction::CmpNeInt cmp; |
| cmp.lhs = c.int_32(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| return Reference(); |
| } |
| |
| } |
| } |
| } |
| |
| left = left.storeOnStack(); |
| right.loadInAccumulator(); |
| |
| switch (oper) { |
| case QSOperator::StrictEqual: { |
| Instruction::CmpStrictEqual cmp; |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| break; |
| } |
| case QSOperator::StrictNotEqual: { |
| Instruction::CmpStrictNotEqual cmp; |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| break; |
| } |
| case QSOperator::Equal: { |
| Instruction::CmpEq cmp; |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| break; |
| } |
| case QSOperator::NotEqual: { |
| Instruction::CmpNe cmp; |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| break; |
| } |
| case QSOperator::Gt: { |
| Instruction::CmpGt cmp; |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| break; |
| } |
| case QSOperator::Ge: { |
| Instruction::CmpGe cmp; |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| break; |
| } |
| case QSOperator::Lt: { |
| Instruction::CmpLt cmp; |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| break; |
| } |
| case QSOperator::Le: { |
| Instruction::CmpLe cmp; |
| cmp.lhs = left.stackSlot(); |
| bytecodeGenerator->addInstruction(cmp); |
| addCJump(); |
| break; |
| } |
| default: |
| Q_UNREACHABLE(); |
| } |
| return Reference(); |
| } |
| |
| bool Codegen::visit(CallExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| Reference base = expression(ast->base); |
| |
| if (hasError()) |
| return false; |
| switch (base.type) { |
| case Reference::Member: |
| case Reference::Subscript: |
| base = base.asLValue(); |
| break; |
| case Reference::Name: |
| break; |
| case Reference::Super: |
| handleConstruct(base, ast->arguments); |
| return false; |
| case Reference::SuperProperty: |
| break; |
| default: |
| base = base.storeOnStack(); |
| break; |
| } |
| |
| int thisObject = bytecodeGenerator->newRegister(); |
| int functionObject = bytecodeGenerator->newRegister(); |
| |
| auto calldata = pushArgs(ast->arguments); |
| if (hasError()) |
| return false; |
| |
| blockTailCalls.unblock(); |
| if (calldata.hasSpread || _tailCallsAreAllowed) { |
| Reference baseObject = base.baseObject(); |
| if (!baseObject.isStackSlot()) { |
| baseObject.storeOnStack(thisObject); |
| baseObject = Reference::fromStackSlot(this, thisObject); |
| } |
| if (!base.isStackSlot()) { |
| base.storeOnStack(functionObject); |
| base = Reference::fromStackSlot(this, functionObject); |
| } |
| |
| if (calldata.hasSpread) { |
| Instruction::CallWithSpread call; |
| call.func = base.stackSlot(); |
| call.thisObject = baseObject.stackSlot(); |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } else { |
| Instruction::TailCall call; |
| call.func = base.stackSlot(); |
| call.thisObject = baseObject.stackSlot(); |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } |
| |
| setExprResult(Reference::fromAccumulator(this)); |
| return false; |
| |
| } |
| |
| handleCall(base, calldata, functionObject, thisObject); |
| return false; |
| } |
| |
| void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject) |
| { |
| //### Do we really need all these call instructions? can's we load the callee in a temp? |
| if (base.type == Reference::Member) { |
| if (!disable_lookups && useFastLookups) { |
| Instruction::CallPropertyLookup call; |
| call.base = base.propertyBase.stackSlot(); |
| call.lookupIndex = registerGetterLookup(base.propertyNameIndex); |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } else { |
| Instruction::CallProperty call; |
| call.base = base.propertyBase.stackSlot(); |
| call.name = base.propertyNameIndex; |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } |
| } else if (base.type == Reference::Subscript) { |
| Instruction::CallElement call; |
| call.base = base.elementBase; |
| call.index = base.elementSubscript.stackSlot(); |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } else if (base.type == Reference::Name) { |
| if (base.name == QStringLiteral("eval")) { |
| Instruction::CallPossiblyDirectEval call; |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } else if (!disable_lookups && useFastLookups && base.global) { |
| if (base.qmlGlobal) { |
| Instruction::CallQmlContextPropertyLookup call; |
| call.index = registerQmlContextPropertyGetterLookup(base.nameAsIndex()); |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } else { |
| Instruction::CallGlobalLookup call; |
| call.index = registerGlobalGetterLookup(base.nameAsIndex()); |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } |
| } else { |
| Instruction::CallName call; |
| call.name = base.nameAsIndex(); |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } |
| } else if (base.type == Reference::SuperProperty) { |
| Reference receiver = base.baseObject(); |
| if (!base.isStackSlot()) { |
| base.storeOnStack(slotForFunction); |
| base = Reference::fromStackSlot(this, slotForFunction); |
| } |
| if (!receiver.isStackSlot()) { |
| receiver.storeOnStack(slotForThisObject); |
| receiver = Reference::fromStackSlot(this, slotForThisObject); |
| } |
| Instruction::CallWithReceiver call; |
| call.name = base.stackSlot(); |
| call.thisObject = receiver.stackSlot(); |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } else { |
| Q_ASSERT(base.isStackSlot()); |
| Instruction::CallValue call; |
| call.name = base.stackSlot(); |
| call.argc = calldata.argc; |
| call.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(call); |
| } |
| |
| setExprResult(Reference::fromAccumulator(this)); |
| } |
| |
| Codegen::Arguments Codegen::pushArgs(ArgumentList *args) |
| { |
| bool hasSpread = false; |
| int argc = 0; |
| for (ArgumentList *it = args; it; it = it->next) { |
| if (it->isSpreadElement) { |
| hasSpread = true; |
| ++argc; |
| } |
| ++argc; |
| } |
| |
| if (!argc) |
| return { 0, 0, false }; |
| |
| int calldata = bytecodeGenerator->newRegisterArray(argc); |
| |
| argc = 0; |
| for (ArgumentList *it = args; it; it = it->next) { |
| if (it->isSpreadElement) { |
| Reference::fromConst( |
| this, |
| StaticValue::emptyValue().asReturnedValue()).storeOnStack(calldata + argc); |
| ++argc; |
| } |
| RegisterScope scope(this); |
| Reference e = expression(it->expression); |
| if (hasError()) |
| break; |
| if (!argc && !it->next && !hasSpread) { |
| // avoid copy for functions taking a single argument |
| if (e.isStackSlot()) |
| return { 1, e.stackSlot(), hasSpread }; |
| } |
| (void) e.storeOnStack(calldata + argc); |
| ++argc; |
| } |
| |
| return { argc, calldata, hasSpread }; |
| } |
| |
| Codegen::Arguments Codegen::pushTemplateArgs(TemplateLiteral *args) |
| { |
| int argc = 0; |
| for (TemplateLiteral *it = args; it; it = it->next) |
| ++argc; |
| |
| if (!argc) |
| return { 0, 0, false }; |
| |
| int calldata = bytecodeGenerator->newRegisterArray(argc); |
| |
| argc = 0; |
| for (TemplateLiteral *it = args; it && it->expression; it = it->next) { |
| RegisterScope scope(this); |
| Reference e = expression(it->expression); |
| if (hasError()) |
| break; |
| (void) e.storeOnStack(calldata + argc); |
| ++argc; |
| } |
| |
| return { argc, calldata, false }; |
| } |
| |
| bool Codegen::visit(ConditionalExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| BytecodeGenerator::Label iftrue = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label iffalse = bytecodeGenerator->newLabel(); |
| condition(ast->expression, &iftrue, &iffalse, true); |
| |
| blockTailCalls.unblock(); |
| |
| iftrue.link(); |
| Reference ok = expression(ast->ok); |
| if (hasError()) |
| return false; |
| ok.loadInAccumulator(); |
| BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump(); |
| |
| iffalse.link(); |
| Reference ko = expression(ast->ko); |
| if (hasError()) { |
| jump_endif.link(); // dummy link, to prevent assert in Jump destructor from triggering |
| return false; |
| } |
| ko.loadInAccumulator(); |
| |
| jump_endif.link(); |
| setExprResult(Reference::fromAccumulator(this)); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(DeleteExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| Reference expr = expression(ast->expression); |
| if (hasError()) |
| return false; |
| |
| switch (expr.type) { |
| case Reference::SuperProperty: |
| // ### this should throw a reference error at runtime. |
| return false; |
| case Reference::StackSlot: |
| if (!expr.stackSlotIsLocalOrArgument) |
| break; |
| // fall through |
| case Reference::ScopedLocal: |
| // Trying to delete a function argument might throw. |
| if (_context->isStrict) { |
| throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode.")); |
| return false; |
| } |
| setExprResult(Reference::fromConst(this, QV4::Encode(false))); |
| return false; |
| case Reference::Name: { |
| if (_context->isStrict) { |
| throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode.")); |
| return false; |
| } |
| Instruction::DeleteName del; |
| del.name = expr.nameAsIndex(); |
| bytecodeGenerator->addInstruction(del); |
| setExprResult(Reference::fromAccumulator(this)); |
| return false; |
| } |
| case Reference::Member: { |
| //### maybe add a variant where the base can be in the accumulator? |
| expr = expr.asLValue(); |
| Instruction::LoadRuntimeString instr; |
| instr.stringId = expr.propertyNameIndex; |
| bytecodeGenerator->addInstruction(instr); |
| Reference index = Reference::fromStackSlot(this); |
| index.storeConsumeAccumulator(); |
| Instruction::DeleteProperty del; |
| del.base = expr.propertyBase.stackSlot(); |
| del.index = index.stackSlot(); |
| bytecodeGenerator->addInstruction(del); |
| setExprResult(Reference::fromAccumulator(this)); |
| return false; |
| } |
| case Reference::Subscript: { |
| //### maybe add a variant where the index can be in the accumulator? |
| expr = expr.asLValue(); |
| Instruction::DeleteProperty del; |
| del.base = expr.elementBase; |
| del.index = expr.elementSubscript.stackSlot(); |
| bytecodeGenerator->addInstruction(del); |
| setExprResult(Reference::fromAccumulator(this)); |
| return false; |
| } |
| default: |
| break; |
| } |
| // [[11.4.1]] Return true if it's not a reference |
| setExprResult(Reference::fromConst(this, QV4::Encode(true))); |
| return false; |
| } |
| |
| bool Codegen::visit(FalseLiteral *) |
| { |
| if (hasError()) |
| return false; |
| |
| setExprResult(Reference::fromConst(this, QV4::Encode(false))); |
| return false; |
| } |
| |
| bool Codegen::visit(SuperLiteral *) |
| { |
| if (hasError()) |
| return false; |
| |
| setExprResult(Reference::fromSuper(this)); |
| return false; |
| } |
| |
| bool Codegen::visit(FieldMemberExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| if (AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(ast->base)) { |
| if (id->name == QLatin1String("new")) { |
| // new.target |
| Q_ASSERT(ast->name == QLatin1String("target")); |
| |
| if (_context->isArrowFunction || _context->contextType == ContextType::Eval) { |
| Reference r = referenceForName(QStringLiteral("new.target"), false); |
| r.isReadonly = true; |
| setExprResult(r); |
| return false; |
| } |
| |
| Reference r = Reference::fromStackSlot(this, CallData::NewTarget); |
| setExprResult(r); |
| return false; |
| } |
| } |
| |
| Reference base = expression(ast->base); |
| if (hasError()) |
| return false; |
| if (base.isSuper()) { |
| Instruction::LoadRuntimeString load; |
| load.stringId = registerString(ast->name.toString()); |
| bytecodeGenerator->addInstruction(load); |
| Reference property = Reference::fromAccumulator(this).storeOnStack(); |
| setExprResult(Reference::fromSuperProperty(property)); |
| return false; |
| } |
| setExprResult(Reference::fromMember(base, ast->name.toString())); |
| return false; |
| } |
| |
| bool Codegen::visit(TaggedTemplate *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| return handleTaggedTemplate(expression(ast->base), ast); |
| } |
| |
| bool Codegen::handleTaggedTemplate(Reference base, TaggedTemplate *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| int functionObject = -1, thisObject = -1; |
| switch (base.type) { |
| case Reference::Member: |
| case Reference::Subscript: |
| base = base.asLValue(); |
| break; |
| case Reference::Name: |
| break; |
| case Reference::SuperProperty: |
| thisObject = bytecodeGenerator->newRegister(); |
| functionObject = bytecodeGenerator->newRegister(); |
| break; |
| default: |
| base = base.storeOnStack(); |
| break; |
| } |
| |
| createTemplateObject(ast->templateLiteral); |
| int templateObjectTemp = Reference::fromAccumulator(this).storeOnStack().stackSlot(); |
| Q_UNUSED(templateObjectTemp); |
| auto calldata = pushTemplateArgs(ast->templateLiteral); |
| if (hasError()) |
| return false; |
| ++calldata.argc; |
| Q_ASSERT(calldata.argv == templateObjectTemp + 1); |
| --calldata.argv; |
| |
| handleCall(base, calldata, functionObject, thisObject); |
| return false; |
| } |
| |
| void Codegen::createTemplateObject(TemplateLiteral *t) |
| { |
| TemplateObject obj; |
| |
| for (TemplateLiteral *it = t; it; it = it->next) { |
| obj.strings.append(registerString(it->value.toString())); |
| obj.rawStrings.append(registerString(it->rawValue.toString())); |
| } |
| |
| int index = _module->templateObjects.size(); |
| _module->templateObjects.append(obj); |
| |
| Instruction::GetTemplateObject getTemplateObject; |
| getTemplateObject.index = index; |
| bytecodeGenerator->addInstruction(getTemplateObject); |
| } |
| |
| bool Codegen::visit(FunctionExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| |
| RegisterScope scope(this); |
| |
| int function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body); |
| if (hasError()) |
| return false; |
| loadClosure(function); |
| setExprResult(Reference::fromAccumulator(this)); |
| return false; |
| } |
| |
| Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs, const SourceLocation &accessLocation) |
| { |
| Context::ResolvedName resolved = _context->resolveName(name, accessLocation); |
| |
| if (resolved.type == Context::ResolvedName::Local || resolved.type == Context::ResolvedName::Stack |
| || resolved.type == Context::ResolvedName::Import) { |
| if (resolved.isArgOrEval && isLhs) |
| // ### add correct source location |
| throwSyntaxError(SourceLocation(), QStringLiteral("Variable name may not be eval or arguments in strict mode")); |
| Reference r; |
| switch (resolved.type) { |
| case Context::ResolvedName::Local: |
| r = Reference::fromScopedLocal(this, resolved.index, resolved.scope); break; |
| case Context::ResolvedName::Stack: |
| r = Reference::fromStackSlot(this, resolved.index, true /*isLocal*/); break; |
| case Context::ResolvedName::Import: |
| r = Reference::fromImport(this, resolved.index); break; |
| default: Q_UNREACHABLE(); |
| } |
| if (r.isStackSlot() && _volatileMemoryLocations.isVolatile(name)) |
| r.isVolatile = true; |
| r.isArgOrEval = resolved.isArgOrEval; |
| r.isReferenceToConst = resolved.isConst; |
| r.requiresTDZCheck = resolved.requiresTDZCheck; |
| r.name = name; // used to show correct name at run-time when TDZ check fails. |
| return r; |
| } |
| |
| Reference r = Reference::fromName(this, name); |
| r.global = useFastLookups && (resolved.type == Context::ResolvedName::Global || resolved.type == Context::ResolvedName::QmlGlobal); |
| r.qmlGlobal = resolved.type == Context::ResolvedName::QmlGlobal; |
| if (!r.global && !r.qmlGlobal && m_globalNames.contains(name)) |
| r.global = true; |
| return r; |
| } |
| |
| void Codegen::loadClosure(int closureId) |
| { |
| if (closureId >= 0) { |
| Instruction::LoadClosure load; |
| load.value = closureId; |
| bytecodeGenerator->addInstruction(load); |
| } else { |
| Reference::fromConst(this, Encode::undefined()).loadInAccumulator(); |
| } |
| } |
| |
| bool Codegen::visit(IdentifierExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| setExprResult(referenceForName(ast->name.toString(), false, ast->firstSourceLocation())); |
| return false; |
| } |
| |
| bool Codegen::visit(NestedExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| accept(ast->expression); |
| return false; |
| } |
| |
| void Codegen::handleConstruct(const Reference &base, ArgumentList *arguments) |
| { |
| Reference constructor; |
| if (base.isSuper()) { |
| Instruction::LoadSuperConstructor super; |
| bytecodeGenerator->addInstruction(super); |
| constructor = Reference::fromAccumulator(this).storeOnStack(); |
| } else { |
| constructor = base.storeOnStack(); |
| } |
| |
| auto calldata = pushArgs(arguments); |
| if (hasError()) |
| return; |
| |
| if (base.isSuper()) |
| Reference::fromStackSlot(this, CallData::NewTarget).loadInAccumulator(); |
| else |
| constructor.loadInAccumulator(); |
| |
| if (calldata.hasSpread) { |
| Instruction::ConstructWithSpread create; |
| create.func = constructor.stackSlot(); |
| create.argc = calldata.argc; |
| create.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(create); |
| } else { |
| Instruction::Construct create; |
| create.func = constructor.stackSlot(); |
| create.argc = calldata.argc; |
| create.argv = calldata.argv; |
| bytecodeGenerator->addInstruction(create); |
| } |
| if (base.isSuper()) |
| // set the result up as the thisObject |
| Reference::fromAccumulator(this).storeOnStack(CallData::This); |
| |
| setExprResult(Reference::fromAccumulator(this)); |
| } |
| |
| bool Codegen::visit(NewExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| Reference base = expression(ast->expression); |
| if (hasError()) |
| return false; |
| if (base.isSuper()) { |
| throwSyntaxError(ast->expression->firstSourceLocation(), QStringLiteral("Cannot use new with super.")); |
| return false; |
| } |
| |
| handleConstruct(base, nullptr); |
| return false; |
| } |
| |
| bool Codegen::visit(NewMemberExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| Reference base = expression(ast->base); |
| if (hasError()) |
| return false; |
| if (base.isSuper()) { |
| throwSyntaxError(ast->base->firstSourceLocation(), QStringLiteral("Cannot use new with super.")); |
| return false; |
| } |
| |
| handleConstruct(base, ast->arguments); |
| return false; |
| } |
| |
| bool Codegen::visit(NotExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| setExprResult(unop(Not, expression(ast->expression))); |
| return false; |
| } |
| |
| bool Codegen::visit(NullExpression *) |
| { |
| if (hasError()) |
| return false; |
| |
| if (exprAccept(cx)) |
| bytecodeGenerator->jump().link(*currentExpr().iffalse()); |
| else |
| setExprResult(Reference::fromConst(this, Encode::null())); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(NumericLiteral *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| setExprResult(Reference::fromConst(this, QV4::Encode::smallestNumber(ast->value))); |
| return false; |
| } |
| |
| bool Codegen::visit(ObjectPattern *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| |
| RegisterScope scope(this); |
| |
| QStringList members; |
| |
| int argc = 0; |
| int args = 0; |
| auto push = [this, &args, &argc](const Reference &arg) { |
| int temp = bytecodeGenerator->newRegister(); |
| if (argc == 0) |
| args = temp; |
| (void) arg.storeOnStack(temp); |
| ++argc; |
| }; |
| |
| PatternPropertyList *it = ast->properties; |
| for (; it; it = it->next) { |
| PatternProperty *p = it->property; |
| AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(p->name); |
| if (cname || p->type != PatternProperty::Literal) |
| break; |
| QString name = p->name->asString(); |
| uint arrayIndex = stringToArrayIndex(name); |
| if (arrayIndex != UINT_MAX) |
| break; |
| if (members.contains(name)) |
| break; |
| members.append(name); |
| |
| { |
| RegisterScope innerScope(this); |
| Reference value = expression(p->initializer, name); |
| if (hasError()) |
| return false; |
| value.loadInAccumulator(); |
| } |
| push(Reference::fromAccumulator(this)); |
| } |
| |
| int classId = jsUnitGenerator->registerJSClass(members); |
| |
| // handle complex property setters |
| for (; it; it = it->next) { |
| PatternProperty *p = it->property; |
| AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(p->name); |
| ObjectLiteralArgument argType = ObjectLiteralArgument::Value; |
| if (p->type == PatternProperty::Method) |
| argType = ObjectLiteralArgument::Method; |
| else if (p->type == PatternProperty::Getter) |
| argType = ObjectLiteralArgument::Getter; |
| else if (p->type == PatternProperty::Setter) |
| argType = ObjectLiteralArgument::Setter; |
| |
| Reference::fromConst(this, Encode(int(argType))).loadInAccumulator(); |
| push(Reference::fromAccumulator(this)); |
| |
| if (cname) { |
| RegisterScope innerScope(this); |
| Reference name = expression(cname->expression); |
| if (hasError()) |
| return false; |
| name.loadInAccumulator(); |
| } else { |
| QString name = p->name->asString(); |
| #if 0 |
| uint arrayIndex = QV4::String::toArrayIndex(name); |
| if (arrayIndex != UINT_MAX) { |
| Reference::fromConst(this, Encode(arrayIndex)).loadInAccumulator(); |
| } else |
| #endif |
| { |
| Instruction::LoadRuntimeString instr; |
| instr.stringId = registerString(name); |
| bytecodeGenerator->addInstruction(instr); |
| } |
| } |
| push(Reference::fromAccumulator(this)); |
| { |
| RegisterScope innerScope(this); |
| if (p->type != PatternProperty::Literal) { |
| // need to get the closure id for the method |
| FunctionExpression *f = p->initializer->asFunctionDefinition(); |
| Q_ASSERT(f); |
| int function = defineFunction(f->name.toString(), f, f->formals, f->body); |
| if (hasError()) |
| return false; |
| Reference::fromConst(this, Encode(function)).loadInAccumulator(); |
| } else { |
| Reference value = expression(p->initializer); |
| if (hasError()) |
| return false; |
| value.loadInAccumulator(); |
| } |
| } |
| push(Reference::fromAccumulator(this)); |
| } |
| |
| Instruction::DefineObjectLiteral call; |
| call.internalClassId = classId; |
| call.argc = argc; |
| call.args = Moth::StackSlot::createRegister(args); |
| bytecodeGenerator->addInstruction(call); |
| setExprResult(Reference::fromAccumulator(this)); |
| return false; |
| } |
| |
| bool Codegen::visit(PostDecrementExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| Reference expr = expression(ast->base); |
| if (hasError()) |
| return false; |
| if (!expr.isLValue()) { |
| throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation")); |
| return false; |
| } |
| if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->decrementToken)) |
| return false; |
| |
| setExprResult(unop(PostDecrement, expr)); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(PostIncrementExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| Reference expr = expression(ast->base); |
| if (hasError()) |
| return false; |
| if (!expr.isLValue()) { |
| throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation")); |
| return false; |
| } |
| if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->incrementToken)) |
| return false; |
| |
| setExprResult(unop(PostIncrement, expr)); |
| return false; |
| } |
| |
| bool Codegen::visit(PreDecrementExpression *ast) |
| { if (hasError()) |
| return false; |
| |
| Reference expr = expression(ast->expression); |
| if (hasError()) |
| return false; |
| if (!expr.isLValue()) { |
| throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix ++ operator applied to value that is not a reference.")); |
| return false; |
| } |
| |
| if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->decrementToken)) |
| return false; |
| setExprResult(unop(PreDecrement, expr)); |
| return false; |
| } |
| |
| bool Codegen::visit(PreIncrementExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| Reference expr = expression(ast->expression); |
| if (hasError()) |
| return false; |
| if (!expr.isLValue()) { |
| throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix ++ operator applied to value that is not a reference.")); |
| return false; |
| } |
| |
| if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->incrementToken)) |
| return false; |
| setExprResult(unop(PreIncrement, expr)); |
| return false; |
| } |
| |
| bool Codegen::visit(RegExpLiteral *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| auto r = Reference::fromStackSlot(this); |
| r.isReadonly = true; |
| setExprResult(r); |
| |
| Instruction::MoveRegExp instr; |
| instr.regExpId = jsUnitGenerator->registerRegExp(ast); |
| instr.destReg = r.stackSlot(); |
| bytecodeGenerator->addInstruction(instr); |
| return false; |
| } |
| |
| bool Codegen::visit(StringLiteral *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| auto r = Reference::fromAccumulator(this); |
| r.isReadonly = true; |
| setExprResult(r); |
| |
| Instruction::LoadRuntimeString instr; |
| instr.stringId = registerString(ast->value.toString()); |
| bytecodeGenerator->addInstruction(instr); |
| return false; |
| } |
| |
| bool Codegen::visit(TemplateLiteral *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| |
| Instruction::LoadRuntimeString instr; |
| instr.stringId = registerString(ast->value.toString()); |
| bytecodeGenerator->addInstruction(instr); |
| |
| if (ast->expression) { |
| RegisterScope scope(this); |
| int temp = bytecodeGenerator->newRegister(); |
| Instruction::StoreReg store; |
| store.reg = temp; |
| bytecodeGenerator->addInstruction(store); |
| |
| Reference expr = expression(ast->expression); |
| if (hasError()) |
| return false; |
| |
| if (ast->next) { |
| int temp2 = bytecodeGenerator->newRegister(); |
| expr.storeOnStack(temp2); |
| visit(ast->next); |
| |
| Instruction::Add instr; |
| instr.lhs = temp2; |
| bytecodeGenerator->addInstruction(instr); |
| } else { |
| expr.loadInAccumulator(); |
| } |
| |
| Instruction::Add instr; |
| instr.lhs = temp; |
| bytecodeGenerator->addInstruction(instr); |
| } |
| |
| auto r = Reference::fromAccumulator(this); |
| r.isReadonly = true; |
| |
| setExprResult(r); |
| return false; |
| |
| } |
| |
| bool Codegen::visit(ThisExpression *) |
| { |
| if (hasError()) |
| return false; |
| |
| if (_context->isArrowFunction) { |
| Reference r = referenceForName(QStringLiteral("this"), false); |
| r.isReadonly = true; |
| setExprResult(r); |
| return false; |
| } |
| setExprResult(Reference::fromThis(this)); |
| return false; |
| } |
| |
| bool Codegen::visit(TildeExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| setExprResult(unop(Compl, expression(ast->expression))); |
| return false; |
| } |
| |
| bool Codegen::visit(TrueLiteral *) |
| { |
| if (hasError()) |
| return false; |
| |
| setExprResult(Reference::fromConst(this, QV4::Encode(true))); |
| return false; |
| } |
| |
| bool Codegen::visit(TypeOfExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| Reference expr = expression(ast->expression); |
| if (hasError()) |
| return false; |
| |
| if (expr.type == Reference::Name) { |
| // special handling as typeof doesn't throw here |
| Instruction::TypeofName instr; |
| instr.name = expr.nameAsIndex(); |
| bytecodeGenerator->addInstruction(instr); |
| } else { |
| expr.loadInAccumulator(); |
| Instruction::TypeofValue instr; |
| bytecodeGenerator->addInstruction(instr); |
| } |
| setExprResult(Reference::fromAccumulator(this)); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(UnaryMinusExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| setExprResult(unop(UMinus, expression(ast->expression))); |
| return false; |
| } |
| |
| bool Codegen::visit(UnaryPlusExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| TailCallBlocker blockTailCalls(this); |
| setExprResult(unop(UPlus, expression(ast->expression))); |
| return false; |
| } |
| |
| bool Codegen::visit(VoidExpression *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| statement(ast->expression); |
| setExprResult(Reference::fromConst(this, Encode::undefined())); |
| return false; |
| } |
| |
| bool Codegen::visit(FunctionDeclaration * ast) |
| { |
| if (hasError()) |
| return false; |
| |
| // no need to block tail calls: the function body isn't visited here. |
| RegisterScope scope(this); |
| |
| if (_functionContext->contextType == ContextType::Binding) |
| referenceForName(ast->name.toString(), true).loadInAccumulator(); |
| exprAccept(nx); |
| return false; |
| } |
| |
| bool Codegen::visit(YieldExpression *ast) |
| { |
| if (inFormalParameterList) { |
| throwSyntaxError(ast->firstSourceLocation(), QLatin1String("yield is not allowed inside parameter lists")); |
| return false; |
| } |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| Reference expr = ast->expression ? expression(ast->expression) : Reference::fromConst(this, Encode::undefined()); |
| if (hasError()) |
| return false; |
| |
| Reference acc = Reference::fromAccumulator(this); |
| |
| if (ast->isYieldStar) { |
| Reference iterator = Reference::fromStackSlot(this); |
| Reference lhsValue = Reference::fromConst(this, Encode::undefined()).storeOnStack(); |
| |
| expr.loadInAccumulator(); |
| Instruction::GetIterator getIterator; |
| getIterator.iterator = static_cast<int>(AST::ForEachType::Of); |
| bytecodeGenerator->addInstruction(getIterator); |
| iterator.storeConsumeAccumulator(); |
| Instruction::LoadUndefined load; |
| bytecodeGenerator->addInstruction(load); |
| |
| BytecodeGenerator::Label in = bytecodeGenerator->newLabel(); |
| bytecodeGenerator->jump().link(in); |
| |
| BytecodeGenerator::Label loop = bytecodeGenerator->label(); |
| |
| lhsValue.loadInAccumulator(); |
| Instruction::YieldStar yield; |
| bytecodeGenerator->addInstruction(yield); |
| |
| in.link(); |
| |
| Instruction::IteratorNextForYieldStar next; |
| next.object = lhsValue.stackSlot(); |
| next.iterator = iterator.stackSlot(); |
| bytecodeGenerator->addInstruction(next); |
| |
| BytecodeGenerator::Jump done = bytecodeGenerator->jumpTrue(); |
| bytecodeGenerator->jumpNotUndefined().link(loop); |
| lhsValue.loadInAccumulator(); |
| emitReturn(acc); |
| |
| |
| done.link(); |
| |
| lhsValue.loadInAccumulator(); |
| setExprResult(acc); |
| return false; |
| } |
| |
| expr.loadInAccumulator(); |
| Instruction::Yield yield; |
| bytecodeGenerator->addInstruction(yield); |
| Instruction::Resume resume; |
| BytecodeGenerator::Jump jump = bytecodeGenerator->addJumpInstruction(resume); |
| emitReturn(acc); |
| jump.link(); |
| setExprResult(acc); |
| return false; |
| } |
| |
| static bool endsWithReturn(Module *module, Node *node) |
| { |
| if (!node) |
| return false; |
| if (AST::cast<ReturnStatement *>(node)) |
| return true; |
| if (AST::cast<ThrowStatement *>(node)) |
| return true; |
| if (Program *p = AST::cast<Program *>(node)) |
| return endsWithReturn(module, p->statements); |
| if (StatementList *sl = AST::cast<StatementList *>(node)) { |
| while (sl->next) |
| sl = sl->next; |
| return endsWithReturn(module, sl->statement); |
| } |
| if (Block *b = AST::cast<Block *>(node)) { |
| Context *blockContext = module->contextMap.value(node); |
| if (blockContext->requiresExecutionContext) |
| // we need to emit a return statement here, because of the |
| // unwind handler |
| return false; |
| return endsWithReturn(module, b->statements); |
| } |
| if (IfStatement *is = AST::cast<IfStatement *>(node)) |
| return is->ko && endsWithReturn(module, is->ok) && endsWithReturn(module, is->ko); |
| return false; |
| } |
| |
| int Codegen::defineFunction(const QString &name, AST::Node *ast, |
| AST::FormalParameterList *formals, |
| AST::StatementList *body) |
| { |
| enterContext(ast); |
| |
| if (_context->functionIndex >= 0) |
| // already defined |
| return leaveContext(); |
| |
| _context->name = name.isEmpty() ? currentExpr().result().name : name; |
| _module->functions.append(_context); |
| _context->functionIndex = _module->functions.count() - 1; |
| |
| Context *savedFunctionContext = _functionContext; |
| _functionContext = _context; |
| ControlFlow *savedControlFlow = controlFlow; |
| controlFlow = nullptr; |
| |
| if (_context->contextType == ContextType::Global || _context->contextType == ContextType::ScriptImportedByQML) { |
| _module->blocks.append(_context); |
| _context->blockIndex = _module->blocks.count() - 1; |
| } |
| if (_module->debugMode) // allow the debugger to see overwritten arguments |
| _context->argumentsCanEscape = true; |
| |
| // When a user writes the following QML signal binding: |
| // onSignal: function() { doSomethingUsefull } |
| // we will generate a binding function that just returns the closure. However, that's not useful |
| // at all, because if the onSignal is a signal handler, the user is actually making it explicit |
| // that the binding is a function, so we should execute that. However, we don't know that during |
| // AOT compilation, so mark the surrounding function as only-returning-a-closure. |
| _context->returnsClosure = body && body->statement && cast<ExpressionStatement *>(body->statement) && cast<FunctionExpression *>(cast<ExpressionStatement *>(body->statement)->expression); |
| |
| BytecodeGenerator bytecode(_context->line, _module->debugMode); |
| BytecodeGenerator *savedBytecodeGenerator; |
| savedBytecodeGenerator = bytecodeGenerator; |
| bytecodeGenerator = &bytecode; |
| bytecodeGenerator->setLocation(ast->firstSourceLocation()); |
| BytecodeGenerator::Label *savedReturnLabel = _returnLabel; |
| _returnLabel = nullptr; |
| |
| bool savedFunctionEndsWithReturn = functionEndsWithReturn; |
| functionEndsWithReturn = endsWithReturn(_module, body); |
| |
| // reserve the js stack frame (Context & js Function & accumulator) |
| bytecodeGenerator->newRegisterArray( |
| sizeof(CallData) / sizeof(StaticValue) - 1 + _context->arguments.size()); |
| |
| bool _inFormalParameterList = false; |
| qSwap(_inFormalParameterList, inFormalParameterList); |
| |
| int returnAddress = -1; |
| bool _requiresReturnValue = _context->requiresImplicitReturnValue(); |
| qSwap(requiresReturnValue, _requiresReturnValue); |
| returnAddress = bytecodeGenerator->newRegister(); |
| qSwap(_returnAddress, returnAddress); |
| |
| // register the lexical scope for global code |
| if (!_context->parent && _context->requiresExecutionContext) { |
| _module->blocks.append(_context); |
| _context->blockIndex = _module->blocks.count() - 1; |
| } |
| |
| TailCallBlocker maybeBlockTailCalls(this, _context->canHaveTailCalls()); |
| |
| RegisterScope registerScope(this); |
| _context->emitBlockHeader(this); |
| |
| { |
| QScopedValueRollback<bool> inFormals(inFormalParameterList, true); |
| TailCallBlocker blockTailCalls(this); // we're not in the FunctionBody or ConciseBody yet |
| |
| int argc = 0; |
| while (formals) { |
| PatternElement *e = formals->element; |
| if (!e) { |
| if (!formals->next) |
| // trailing comma |
| break; |
| Q_UNREACHABLE(); |
| } |
| |
| Reference arg = referenceForName(e->bindingIdentifier.toString(), true); |
| if (e->type == PatternElement::RestElement) { |
| Q_ASSERT(!formals->next); |
| Instruction::CreateRestParameter rest; |
| rest.argIndex = argc; |
| bytecodeGenerator->addInstruction(rest); |
| arg.storeConsumeAccumulator(); |
| } else { |
| if (e->bindingTarget || e->initializer) { |
| initializeAndDestructureBindingElement(e, arg); |
| if (hasError()) |
| break; |
| } |
| } |
| formals = formals->next; |
| ++argc; |
| } |
| } |
| |
| if (_context->isGenerator) { |
| Instruction::Yield yield; |
| bytecodeGenerator->addInstruction(yield); |
| } |
| |
| statementList(body); |
| |
| if (!hasError()) { |
| bytecodeGenerator->setLocation(ast->lastSourceLocation()); |
| _context->emitBlockFooter(this); |
| |
| if (_returnLabel || !functionEndsWithReturn) { |
| if (_returnLabel) |
| _returnLabel->link(); |
| |
| if (_returnLabel || requiresReturnValue) { |
| Instruction::LoadReg load; |
| load.reg = Moth::StackSlot::createRegister(_returnAddress); |
| bytecodeGenerator->addInstruction(load); |
| } else { |
| Reference::fromConst(this, Encode::undefined()).loadInAccumulator(); |
| } |
| |
| bytecodeGenerator->addInstruction(Instruction::Ret()); |
| } |
| |
| Q_ASSERT(_context == _functionContext); |
| bytecodeGenerator->finalize(_context); |
| _context->registerCountInFunction = bytecodeGenerator->registerCount(); |
| static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); |
| if (showCode) { |
| qDebug() << "=== Bytecode for" << _context->name << "strict mode" << _context->isStrict |
| << "register count" << _context->registerCountInFunction << "implicit return" << requiresReturnValue; |
| QV4::Moth::dumpBytecode(_context->code, _context->locals.size(), _context->arguments.size(), |
| _context->line, _context->lineNumberMapping); |
| qDebug(); |
| } |
| } |
| |
| qSwap(_returnAddress, returnAddress); |
| qSwap(requiresReturnValue, _requiresReturnValue); |
| qSwap(_inFormalParameterList, inFormalParameterList); |
| bytecodeGenerator = savedBytecodeGenerator; |
| delete _returnLabel; |
| _returnLabel = savedReturnLabel; |
| controlFlow = savedControlFlow; |
| functionEndsWithReturn = savedFunctionEndsWithReturn; |
| _functionContext = savedFunctionContext; |
| |
| return leaveContext(); |
| } |
| |
| bool Codegen::visit(Block *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| |
| ControlFlowBlock controlFlow(this, ast); |
| statementList(ast->statements); |
| return false; |
| } |
| |
| bool Codegen::visit(BreakStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| // no need to block tail calls here: children aren't visited |
| if (!controlFlow) { |
| throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Break outside of loop")); |
| return false; |
| } |
| |
| ControlFlow::UnwindTarget target = controlFlow->unwindTarget(ControlFlow::Break, ast->label.toString()); |
| if (!target.linkLabel.isValid()) { |
| if (ast->label.isEmpty()) |
| throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Break outside of loop")); |
| else |
| throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString())); |
| return false; |
| } |
| |
| bytecodeGenerator->unwindToLabel(target.unwindLevel, target.linkLabel); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(ContinueStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| // no need to block tail calls here: children aren't visited |
| RegisterScope scope(this); |
| |
| if (!controlFlow) { |
| throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Continue outside of loop")); |
| return false; |
| } |
| |
| ControlFlow::UnwindTarget target = controlFlow->unwindTarget(ControlFlow::Continue, ast->label.toString()); |
| if (!target.linkLabel.isValid()) { |
| if (ast->label.isEmpty()) |
| throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString())); |
| else |
| throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("continue outside of loop")); |
| return false; |
| } |
| |
| bytecodeGenerator->unwindToLabel(target.unwindLevel, target.linkLabel); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(DebuggerStatement *) |
| { |
| Q_UNIMPLEMENTED(); |
| return false; |
| } |
| |
| bool Codegen::visit(DoWhileStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| |
| BytecodeGenerator::Label body = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label cond = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); |
| |
| ControlFlowLoop flow(this, &end, &cond); |
| |
| // special case that is not a loop: |
| // do {...} while (false) |
| if (!AST::cast<FalseLiteral *>(ast->expression)) |
| bytecodeGenerator->addLoopStart(body); |
| |
| body.link(); |
| statement(ast->statement); |
| setJumpOutLocation(bytecodeGenerator, ast->statement, ast->semicolonToken); |
| |
| cond.link(); |
| if (AST::cast<TrueLiteral *>(ast->expression)) { |
| // do {} while (true) -> just jump back to the loop body, no need to generate a condition |
| bytecodeGenerator->checkException(); |
| bytecodeGenerator->jump().link(body); |
| } else if (AST::cast<FalseLiteral *>(ast->expression)) { |
| // do {} while (false) -> fall through, no need to generate a condition |
| } else { |
| TailCallBlocker blockTailCalls(this); |
| bytecodeGenerator->checkException(); |
| condition(ast->expression, &body, &end, false); |
| } |
| |
| end.link(); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(EmptyStatement *) |
| { |
| return false; |
| } |
| |
| bool Codegen::visit(ExpressionStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| if (requiresReturnValue) { |
| Reference e = expression(ast->expression); |
| if (hasError()) |
| return false; |
| (void) e.storeOnStack(_returnAddress); |
| } else { |
| statement(ast->expression); |
| } |
| return false; |
| } |
| |
| bool Codegen::visit(ForEachStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| Reference iterator = Reference::fromStackSlot(this); |
| Reference iteratorDone = Reference::fromConst(this, Encode(false)).storeOnStack(); |
| Reference lhsValue = Reference::fromStackSlot(this); |
| |
| // There should be a temporal block, so that variables declared in lhs shadow outside vars. |
| // This block should define a temporal dead zone for those variables. |
| { |
| RegisterScope innerScope(this); |
| ControlFlowBlock controlFlow(this, ast); |
| Reference expr = expression(ast->expression); |
| if (hasError()) |
| return false; |
| |
| expr.loadInAccumulator(); |
| Instruction::GetIterator iteratorObjInstr; |
| iteratorObjInstr.iterator = static_cast<int>(ast->type); |
| bytecodeGenerator->addInstruction(iteratorObjInstr); |
| iterator.storeConsumeAccumulator(); |
| } |
| |
| BytecodeGenerator::Label in = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); |
| |
| { |
| auto cleanup = [ast, iterator, iteratorDone, this]() { |
| if (ast->type == ForEachType::Of) { |
| iterator.loadInAccumulator(); |
| Instruction::IteratorClose close; |
| close.done = iteratorDone.stackSlot(); |
| bytecodeGenerator->addInstruction(close); |
| } |
| }; |
| ControlFlowLoop flow(this, &end, &in, cleanup); |
| bytecodeGenerator->addLoopStart(in); |
| in.link(); |
| iterator.loadInAccumulator(); |
| Instruction::IteratorNext next; |
| next.value = lhsValue.stackSlot(); |
| next.done = iteratorDone.stackSlot(); |
| bytecodeGenerator->addInstruction(next); |
| bytecodeGenerator->addJumpInstruction(Instruction::JumpTrue()).link(end); |
| |
| // each iteration gets it's own context, as per spec |
| { |
| RegisterScope innerScope(this); |
| ControlFlowBlock controlFlow(this, ast); |
| |
| if (ExpressionNode *e = ast->lhs->expressionCast()) { |
| if (AST::Pattern *p = e->patternCast()) { |
| RegisterScope scope(this); |
| destructurePattern(p, lhsValue); |
| } else { |
| Reference lhs = expression(e); |
| if (hasError()) |
| goto error; |
| if (!lhs.isLValue()) { |
| throwReferenceError(e->firstSourceLocation(), QStringLiteral("Invalid left-hand side expression for 'in' expression")); |
| goto error; |
| } |
| lhs = lhs.asLValue(); |
| lhsValue.loadInAccumulator(); |
| lhs.storeConsumeAccumulator(); |
| } |
| } else if (PatternElement *p = AST::cast<PatternElement *>(ast->lhs)) { |
| initializeAndDestructureBindingElement(p, lhsValue, /*isDefinition =*/ true); |
| if (hasError()) |
| goto error; |
| } else { |
| Q_UNREACHABLE(); |
| } |
| |
| blockTailCalls.unblock(); |
| statement(ast->statement); |
| setJumpOutLocation(bytecodeGenerator, ast->statement, ast->forToken); |
| } |
| |
| bytecodeGenerator->checkException(); |
| bytecodeGenerator->jump().link(in); |
| |
| error: |
| end.link(); |
| |
| // all execution paths need to end up here (normal loop exit, break, and exceptions) in |
| // order to reset the unwind handler, and to close the iterator in calse of an for-of loop. |
| } |
| |
| return false; |
| } |
| |
| bool Codegen::visit(ForStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| ControlFlowBlock controlFlow(this, ast); |
| |
| if (ast->initialiser) |
| statement(ast->initialiser); |
| else if (ast->declarations) |
| variableDeclarationList(ast->declarations); |
| |
| BytecodeGenerator::Label cond = bytecodeGenerator->label(); |
| BytecodeGenerator::Label body = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label step = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); |
| |
| ControlFlowLoop flow(this, &end, &step); |
| bytecodeGenerator->addLoopStart(cond); |
| condition(ast->condition, &body, &end, true); |
| |
| body.link(); |
| blockTailCalls.unblock(); |
| statement(ast->statement); |
| blockTailCalls.reblock(); |
| setJumpOutLocation(bytecodeGenerator, ast->statement, ast->forToken); |
| |
| step.link(); |
| if (_context->requiresExecutionContext) { |
| Instruction::CloneBlockContext clone; |
| bytecodeGenerator->addInstruction(clone); |
| } |
| statement(ast->expression); |
| bytecodeGenerator->checkException(); |
| bytecodeGenerator->jump().link(cond); |
| |
| end.link(); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(IfStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| BytecodeGenerator::Label trueLabel = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label falseLabel = bytecodeGenerator->newLabel(); |
| condition(ast->expression, &trueLabel, &falseLabel, true); |
| blockTailCalls.unblock(); |
| |
| trueLabel.link(); |
| statement(ast->ok); |
| if (ast->ko) { |
| if (endsWithReturn(_module, ast)) { |
| falseLabel.link(); |
| statement(ast->ko); |
| } else { |
| BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump(); |
| falseLabel.link(); |
| statement(ast->ko); |
| jump_endif.link(); |
| } |
| } else { |
| falseLabel.link(); |
| } |
| |
| return false; |
| } |
| |
| bool Codegen::visit(LabelledStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| |
| // check that no outer loop contains the label |
| ControlFlow *l = controlFlow; |
| while (l) { |
| if (l->label() == ast->label) { |
| QString error = QString(QStringLiteral("Label '%1' has already been declared")).arg(ast->label.toString()); |
| throwSyntaxError(ast->firstSourceLocation(), error); |
| return false; |
| } |
| l = l->parent; |
| } |
| _labelledStatement = ast; |
| |
| if (AST::cast<AST::SwitchStatement *>(ast->statement) || |
| AST::cast<AST::WhileStatement *>(ast->statement) || |
| AST::cast<AST::DoWhileStatement *>(ast->statement) || |
| AST::cast<AST::ForStatement *>(ast->statement) || |
| AST::cast<AST::ForEachStatement *>(ast->statement)) { |
| statement(ast->statement); // labelledStatement will be associated with the ast->statement's loop. |
| } else { |
| BytecodeGenerator::Label breakLabel = bytecodeGenerator->newLabel(); |
| ControlFlowLoop flow(this, &breakLabel); |
| statement(ast->statement); |
| breakLabel.link(); |
| } |
| |
| return false; |
| } |
| |
| void Codegen::emitReturn(const Reference &expr) |
| { |
| ControlFlow::UnwindTarget target = controlFlow ? controlFlow->unwindTarget(ControlFlow::Return) : ControlFlow::UnwindTarget(); |
| if (target.linkLabel.isValid() && target.unwindLevel) { |
| Q_ASSERT(_returnAddress >= 0); |
| (void) expr.storeOnStack(_returnAddress); |
| bytecodeGenerator->unwindToLabel(target.unwindLevel, target.linkLabel); |
| } else { |
| expr.loadInAccumulator(); |
| bytecodeGenerator->addInstruction(Instruction::Ret()); |
| } |
| } |
| |
| bool Codegen::visit(ReturnStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| if (_functionContext->contextType != ContextType::Function && _functionContext->contextType != ContextType::Binding) { |
| throwSyntaxError(ast->returnToken, QStringLiteral("Return statement outside of function")); |
| return false; |
| } |
| Reference expr; |
| if (ast->expression) { |
| expr = expression(ast->expression); |
| if (hasError()) |
| return false; |
| } else { |
| expr = Reference::fromConst(this, Encode::undefined()); |
| } |
| |
| emitReturn(expr); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(SwitchStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| if (requiresReturnValue) |
| Reference::fromConst(this, Encode::undefined()).storeOnStack(_returnAddress); |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| if (ast->block) { |
| BytecodeGenerator::Label switchEnd = bytecodeGenerator->newLabel(); |
| |
| Reference lhs = expression(ast->expression); |
| if (hasError()) |
| return false; |
| lhs = lhs.storeOnStack(); |
| |
| ControlFlowBlock controlFlow(this, ast->block); |
| |
| // set up labels for all clauses |
| QHash<Node *, BytecodeGenerator::Label> blockMap; |
| for (CaseClauses *it = ast->block->clauses; it; it = it->next) |
| blockMap[it->clause] = bytecodeGenerator->newLabel(); |
| if (ast->block->defaultClause) |
| blockMap[ast->block->defaultClause] = bytecodeGenerator->newLabel(); |
| for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) |
| blockMap[it->clause] = bytecodeGenerator->newLabel(); |
| |
| // do the switch conditions |
| for (CaseClauses *it = ast->block->clauses; it; it = it->next) { |
| CaseClause *clause = it->clause; |
| Reference rhs = expression(clause->expression); |
| if (hasError()) |
| return false; |
| rhs.loadInAccumulator(); |
| bytecodeGenerator->jumpStrictEqual(lhs.stackSlot(), blockMap.value(clause)); |
| } |
| |
| for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) { |
| CaseClause *clause = it->clause; |
| Reference rhs = expression(clause->expression); |
| if (hasError()) |
| return false; |
| rhs.loadInAccumulator(); |
| bytecodeGenerator->jumpStrictEqual(lhs.stackSlot(), blockMap.value(clause)); |
| } |
| |
| if (DefaultClause *defaultClause = ast->block->defaultClause) |
| bytecodeGenerator->jump().link(blockMap.value(defaultClause)); |
| else |
| bytecodeGenerator->jump().link(switchEnd); |
| |
| ControlFlowLoop flow(this, &switchEnd); |
| |
| insideSwitch = true; |
| blockTailCalls.unblock(); |
| for (CaseClauses *it = ast->block->clauses; it; it = it->next) { |
| CaseClause *clause = it->clause; |
| blockMap[clause].link(); |
| |
| statementList(clause->statements); |
| } |
| |
| if (ast->block->defaultClause) { |
| DefaultClause *clause = ast->block->defaultClause; |
| blockMap[clause].link(); |
| |
| statementList(clause->statements); |
| } |
| |
| for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) { |
| CaseClause *clause = it->clause; |
| blockMap[clause].link(); |
| |
| statementList(clause->statements); |
| } |
| insideSwitch = false; |
| |
| switchEnd.link(); |
| |
| } |
| |
| return false; |
| } |
| |
| bool Codegen::visit(ThrowStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| Reference expr = expression(ast->expression); |
| if (hasError()) |
| return false; |
| |
| expr.loadInAccumulator(); |
| Instruction::ThrowException instr; |
| bytecodeGenerator->addInstruction(instr); |
| return false; |
| } |
| |
| void Codegen::handleTryCatch(TryStatement *ast) |
| { |
| Q_ASSERT(ast); |
| RegisterScope scope(this); |
| { |
| ControlFlowCatch catchFlow(this, ast->catchExpression); |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); // IMPORTANT: destruction will unblock tail calls before catch is generated |
| statement(ast->statement); |
| } |
| } |
| |
| void Codegen::handleTryFinally(TryStatement *ast) |
| { |
| RegisterScope scope(this); |
| ControlFlowFinally finally(this, ast->finallyExpression); |
| TailCallBlocker blockTailCalls(this); // IMPORTANT: destruction will unblock tail calls before finally is generated |
| |
| if (ast->catchExpression) { |
| handleTryCatch(ast); |
| } else { |
| RegisterScope scope(this); |
| statement(ast->statement); |
| } |
| } |
| |
| bool Codegen::visit(TryStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| |
| if (ast->finallyExpression && ast->finallyExpression->statement) { |
| handleTryFinally(ast); |
| } else { |
| handleTryCatch(ast); |
| } |
| |
| return false; |
| } |
| |
| bool Codegen::visit(VariableStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| variableDeclarationList(ast->declarations); |
| return false; |
| } |
| |
| bool Codegen::visit(WhileStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| if (AST::cast<FalseLiteral *>(ast->expression)) |
| return false; |
| |
| RegisterScope scope(this); |
| |
| BytecodeGenerator::Label start = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); |
| BytecodeGenerator::Label cond = bytecodeGenerator->label(); |
| ControlFlowLoop flow(this, &end, &cond); |
| bytecodeGenerator->addLoopStart(cond); |
| |
| bytecodeGenerator->checkException(); |
| |
| if (!AST::cast<TrueLiteral *>(ast->expression)) { |
| TailCallBlocker blockTailCalls(this); |
| condition(ast->expression, &start, &end, true); |
| } |
| |
| start.link(); |
| statement(ast->statement); |
| setJumpOutLocation(bytecodeGenerator, ast->statement, ast->whileToken); |
| bytecodeGenerator->jump().link(cond); |
| |
| end.link(); |
| return false; |
| } |
| |
| bool Codegen::visit(WithStatement *ast) |
| { |
| if (hasError()) |
| return false; |
| |
| RegisterScope scope(this); |
| TailCallBlocker blockTailCalls(this); |
| |
| Reference src = expression(ast->expression); |
| if (hasError()) |
| return false; |
| src = src.storeOnStack(); // trigger load before we setup the exception handler, so exceptions here go to the right place |
| src.loadInAccumulator(); |
| |
| enterContext(ast); |
| { |
| blockTailCalls.unblock(); |
| ControlFlowWith flow(this); |
| statement(ast->statement); |
| } |
| leaveContext(); |
| |
| return false; |
| } |
| |
| bool Codegen::visit(UiArrayBinding *) |
| { |
| Q_UNIMPLEMENTED(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiObjectBinding *) |
| { |
| Q_UNIMPLEMENTED(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiObjectDefinition *) |
| { |
| Q_UNIMPLEMENTED(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiPublicMember *) |
| { |
| Q_UNIMPLEMENTED(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiScriptBinding *) |
| { |
| Q_UNIMPLEMENTED(); |
| return false; |
| } |
| |
| bool Codegen::visit(UiSourceElement *) |
| { |
| Q_UNIMPLEMENTED(); |
| return false; |
| } |
| |
| bool Codegen::throwSyntaxErrorOnEvalOrArgumentsInStrictMode(const Reference &r, const SourceLocation& loc) |
| { |
| if (!_context->isStrict) |
| return false; |
| bool isArgOrEval = false; |
| if (r.type == Reference::Name) { |
| QString str = jsUnitGenerator->stringForIndex(r.nameAsIndex()); |
| if (str == QLatin1String("eval") || str == QLatin1String("arguments")) { |
| isArgOrEval = true; |
| } |
| } else if (r.type == Reference::ScopedLocal || r.isRegister()) { |
| isArgOrEval = r.isArgOrEval; |
| } |
| if (isArgOrEval) |
| throwSyntaxError(loc, QStringLiteral("Variable name may not be eval or arguments in strict mode")); |
| return isArgOrEval; |
| } |
| |
| void Codegen::throwError(ErrorType errorType, const SourceLocation &loc, const QString &detail) |
| { |
| if (hasError()) |
| return; |
| |
| _errorType = errorType; |
| _error.message = detail; |
| _error.loc = loc; |
| } |
| |
| void Codegen::throwSyntaxError(const SourceLocation &loc, const QString &detail) |
| { |
| throwError(SyntaxError, loc, detail); |
| } |
| |
| void Codegen::throwReferenceError(const SourceLocation &loc, const QString &detail) |
| { |
| throwError(ReferenceError, loc, detail); |
| } |
| |
| QQmlJS::DiagnosticMessage Codegen::error() const |
| { |
| return _error; |
| } |
| |
| QV4::CompiledData::CompilationUnit Codegen::generateCompilationUnit( |
| bool generateUnitData) |
| { |
| return QV4::CompiledData::CompilationUnit( |
| generateUnitData ? jsUnitGenerator->generateUnit() : nullptr); |
| } |
| |
| CompiledData::CompilationUnit Codegen::compileModule( |
| bool debugMode, const QString &url, const QString &sourceCode, |
| const QDateTime &sourceTimeStamp, QList<QQmlJS::DiagnosticMessage> *diagnostics) |
| { |
| QQmlJS::Engine ee; |
| QQmlJS::Lexer lexer(&ee); |
| lexer.setCode(sourceCode, /*line*/1, /*qml mode*/false); |
| QQmlJS::Parser parser(&ee); |
| |
| const bool parsed = parser.parseModule(); |
| |
| if (diagnostics) |
| *diagnostics = parser.diagnosticMessages(); |
| |
| if (!parsed) |
| return CompiledData::CompilationUnit(); |
| |
| QQmlJS::AST::ESModule *moduleNode = QQmlJS::AST::cast<QQmlJS::AST::ESModule*>(parser.rootNode()); |
| if (!moduleNode) { |
| // if parsing was successful, and we have no module, then |
| // the file was empty. |
| if (diagnostics) |
| diagnostics->clear(); |
| return nullptr; |
| } |
| |
| using namespace QV4::Compiler; |
| Compiler::Module compilerModule(debugMode); |
| compilerModule.unitFlags |= CompiledData::Unit::IsESModule; |
| compilerModule.sourceTimeStamp = sourceTimeStamp; |
| JSUnitGenerator jsGenerator(&compilerModule); |
| Codegen cg(&jsGenerator, /*strictMode*/true); |
| cg.generateFromModule(url, url, sourceCode, moduleNode, &compilerModule); |
| if (cg.hasError()) { |
| if (diagnostics) |
| *diagnostics << cg.error(); |
| return CompiledData::CompilationUnit(); |
| } |
| |
| return cg.generateCompilationUnit(); |
| } |
| |
| class Codegen::VolatileMemoryLocationScanner: protected QQmlJS::AST::Visitor |
| { |
| VolatileMemoryLocations locs; |
| Codegen *parent; |
| |
| public: |
| VolatileMemoryLocationScanner(Codegen *parent) : |
| QQmlJS::AST::Visitor(parent->recursionDepth()), |
| parent(parent) |
| {} |
| |
| Codegen::VolatileMemoryLocations scan(AST::Node *s) |
| { |
| s->accept(this); |
| return locs; |
| } |
| |
| bool visit(ArrayMemberExpression *) override |
| { |
| locs.setAllVolatile(); |
| return false; |
| } |
| |
| bool visit(FieldMemberExpression *) override |
| { |
| locs.setAllVolatile(); |
| return false; |
| } |
| |
| bool visit(PostIncrementExpression *e) override |
| { |
| collectIdentifiers(locs.specificLocations, e->base); |
| return false; |
| } |
| |
| bool visit(PostDecrementExpression *e) override |
| { |
| collectIdentifiers(locs.specificLocations, e->base); |
| return false; |
| } |
| |
| bool visit(PreIncrementExpression *e) override |
| { |
| collectIdentifiers(locs.specificLocations, e->expression); |
| return false; |
| } |
| |
| bool visit(PreDecrementExpression *e) override |
| { |
| collectIdentifiers(locs.specificLocations, e->expression); |
| return false; |
| } |
| |
| bool visit(BinaryExpression *e) override |
| { |
| switch (e->op) { |
| case QSOperator::InplaceAnd: |
| case QSOperator::InplaceSub: |
| case QSOperator::InplaceDiv: |
| case QSOperator::InplaceAdd: |
| case QSOperator::InplaceLeftShift: |
| case QSOperator::InplaceMod: |
| case QSOperator::InplaceMul: |
| case QSOperator::InplaceOr: |
| case QSOperator::InplaceRightShift: |
| case QSOperator::InplaceURightShift: |
| case QSOperator::InplaceXor: |
| collectIdentifiers(locs.specificLocations, e); |
| return false; |
| |
| default: |
| return true; |
| } |
| } |
| |
| void throwRecursionDepthError() override |
| { |
| parent->throwRecursionDepthError(); |
| } |
| |
| private: |
| void collectIdentifiers(QVector<QStringView> &ids, AST::Node *node) { |
| class Collector: public QQmlJS::AST::Visitor { |
| private: |
| QVector<QStringView> &ids; |
| VolatileMemoryLocationScanner *parent; |
| |
| public: |
| Collector(QVector<QStringView> &ids, VolatileMemoryLocationScanner *parent) : |
| QQmlJS::AST::Visitor(parent->recursionDepth()), ids(ids), parent(parent) |
| {} |
| |
| bool visit(IdentifierExpression *ie) final { |
| ids.append(ie->name); |
| return false; |
| } |
| |
| void throwRecursionDepthError() final |
| { |
| parent->throwRecursionDepthError(); |
| } |
| }; |
| Collector collector(ids, this); |
| node->accept(&collector); |
| } |
| }; |
| |
| Codegen::VolatileMemoryLocations Codegen::scanVolatileMemoryLocations(AST::Node *ast) |
| { |
| VolatileMemoryLocationScanner scanner(this); |
| return scanner.scan(ast); |
| } |
| |
| QUrl Codegen::url() const |
| { |
| return QUrl(_fileNameIsUrl ? QUrl(_module->fileName) : QUrl::fromLocalFile(_module->fileName)); |
| } |
| |
| bool Codegen::RValue::operator==(const RValue &other) const |
| { |
| switch (type) { |
| case Accumulator: |
| return other.isAccumulator(); |
| case StackSlot: |
| return other.isStackSlot() && theStackSlot == other.theStackSlot; |
| case Const: |
| return other.isConst() && constant == other.constant; |
| default: |
| return false; |
| } |
| } |
| |
| Codegen::RValue Codegen::RValue::storeOnStack() const |
| { |
| switch (type) { |
| case Accumulator: |
| return RValue::fromStackSlot(codegen, Reference::fromAccumulator(codegen).storeOnStack().stackSlot()); |
| case StackSlot: |
| return *this; |
| case Const: |
| return RValue::fromStackSlot(codegen, Reference::storeConstOnStack(codegen, constant).stackSlot()); |
| default: |
| Q_UNREACHABLE(); |
| } |
| } |
| |
| void Codegen::RValue::loadInAccumulator() const |
| { |
| switch (type) { |
| case Accumulator: |
| // nothing to do |
| return; |
| case StackSlot: |
| return Reference::fromStackSlot(codegen, theStackSlot).loadInAccumulator(); |
| case Const: |
| return Reference::fromConst(codegen, constant).loadInAccumulator(); |
| default: |
| Q_UNREACHABLE(); |
| } |
| |
| } |
| |
| bool Codegen::Reference::operator==(const Codegen::Reference &other) const |
| { |
| if (type != other.type) |
| return false; |
| switch (type) { |
| case Invalid: |
| case Accumulator: |
| break; |
| case Super: |
| return true; |
| case SuperProperty: |
| return property == other.property; |
| case StackSlot: |
| return theStackSlot == other.theStackSlot; |
| case ScopedLocal: |
| return index == other.index && scope == other.scope; |
| case Name: |
| return nameAsIndex() == other.nameAsIndex(); |
| case Member: |
| return propertyBase == other.propertyBase && propertyNameIndex == other.propertyNameIndex; |
| case Subscript: |
| return elementBase == other.elementBase && elementSubscript == other.elementSubscript; |
| case Import: |
| return index == other.index; |
| case Const: |
| return constant == other.constant; |
| } |
| return true; |
| } |
| |
| Codegen::RValue Codegen::Reference::asRValue() const |
| { |
| switch (type) { |
| case Invalid: |
| Q_UNREACHABLE(); |
| case Accumulator: |
| return RValue::fromAccumulator(codegen); |
| case StackSlot: |
| return RValue::fromStackSlot(codegen, stackSlot()); |
| case Const: |
| return RValue::fromConst(codegen, constant); |
| default: |
| loadInAccumulator(); |
| return RValue::fromAccumulator(codegen); |
| } |
| } |
| |
| Codegen::Reference Codegen::Reference::asLValue() const |
| { |
| switch (type) { |
| case Invalid: |
| case Accumulator: |
| Q_UNREACHABLE(); |
| case Super: |
| codegen->throwSyntaxError(SourceLocation(), QStringLiteral("Super lvalues not implemented.")); |
| return *this; |
| case Member: |
| if (!propertyBase.isStackSlot()) { |
| Reference r = *this; |
| r.propertyBase = propertyBase.storeOnStack(); |
| return r; |
| } |
| return *this; |
| case Subscript: |
| if (!elementSubscript.isStackSlot()) { |
| Reference r = *this; |
| r.elementSubscript = elementSubscript.storeOnStack(); |
| return r; |
| } |
| return *this; |
| default: |
| return *this; |
| } |
| } |
| |
| Codegen::Reference Codegen::Reference::storeConsumeAccumulator() const |
| { |
| storeAccumulator(); // it doesn't matter what happens here, just do it. |
| return Reference(); |
| } |
| |
| Codegen::Reference Codegen::Reference::baseObject() const |
| { |
| if (type == Reference::Member) { |
| RValue rval = propertyBase; |
| if (!rval.isValid()) |
| return Reference::fromConst(codegen, Encode::undefined()); |
| if (rval.isAccumulator()) |
| return Reference::fromAccumulator(codegen); |
| if (rval.isStackSlot()) |
| return Reference::fromStackSlot(codegen, rval.stackSlot()); |
| if (rval.isConst()) |
| return Reference::fromConst(codegen, rval.constantValue()); |
| Q_UNREACHABLE(); |
| } else if (type == Reference::Subscript) { |
| return Reference::fromStackSlot(codegen, elementBase.stackSlot()); |
| } else if (type == Reference::SuperProperty) { |
| return Reference::fromStackSlot(codegen, CallData::This); |
| } else { |
| return Reference::fromConst(codegen, Encode::undefined()); |
| } |
| } |
| |
| Codegen::Reference Codegen::Reference::storeOnStack() const |
| { return doStoreOnStack(-1); } |
| |
| void Codegen::Reference::storeOnStack(int slotIndex) const |
| { doStoreOnStack(slotIndex); } |
| |
| Codegen::Reference Codegen::Reference::doStoreOnStack(int slotIndex) const |
| { |
| Q_ASSERT(isValid()); |
| |
| if (isStackSlot() && slotIndex == -1 && !(stackSlotIsLocalOrArgument && isVolatile) && !requiresTDZCheck) |
| return *this; |
| |
| if (isStackSlot() && !requiresTDZCheck) { // temp-to-temp move |
| Reference dest = Reference::fromStackSlot(codegen, slotIndex); |
| Instruction::MoveReg move; |
| move.srcReg = stackSlot(); |
| move.destReg = dest.stackSlot(); |
| codegen->bytecodeGenerator->addInstruction(move); |
| return dest; |
| } |
| |
| Reference slot = Reference::fromStackSlot(codegen, slotIndex); |
| if (isConstant()) { |
| Instruction::MoveConst move; |
| move.constIndex = codegen->registerConstant(constant); |
| move.destTemp = slot.stackSlot(); |
| codegen->bytecodeGenerator->addInstruction(move); |
| } else { |
| loadInAccumulator(); |
| slot.storeConsumeAccumulator(); |
| } |
| return slot; |
| } |
| |
| Codegen::Reference Codegen::Reference::storeRetainAccumulator() const |
| { |
| if (storeWipesAccumulator()) { |
| // a store will |
| auto tmp = Reference::fromStackSlot(codegen); |
| tmp.storeAccumulator(); // this is safe, and won't destory the accumulator |
| storeAccumulator(); |
| return tmp; |
| } else { |
| // ok, this is safe, just do the store. |
| storeAccumulator(); |
| return *this; |
| } |
| } |
| |
| bool Codegen::Reference::storeWipesAccumulator() const |
| { |
| switch (type) { |
| default: |
| case Invalid: |
| case Const: |
| case Accumulator: |
| Q_UNREACHABLE(); |
| return false; |
| case StackSlot: |
| case ScopedLocal: |
| return false; |
| case Name: |
| case Member: |
| case Subscript: |
| return true; |
| } |
| } |
| |
| void Codegen::Reference::storeAccumulator() const |
| { |
| if (isReferenceToConst) { |
| // throw a type error |
| RegisterScope scope(codegen); |
| Reference r = codegen->referenceForName(QStringLiteral("TypeError"), false); |
| r = r.storeOnStack(); |
| Instruction::Construct construct; |
| construct.func = r.stackSlot(); |
| construct.argc = 0; |
| construct.argv = 0; |
| codegen->bytecodeGenerator->addInstruction(construct); |
| Instruction::ThrowException throwException; |
| codegen->bytecodeGenerator->addInstruction(throwException); |
| return; |
| } |
| switch (type) { |
| case Super: |
| Q_UNREACHABLE(); |
| return; |
| case SuperProperty: |
| Instruction::StoreSuperProperty store; |
| store.property = property.stackSlot(); |
| codegen->bytecodeGenerator->addInstruction(store); |
| return; |
| case StackSlot: { |
| Instruction::StoreReg store; |
| store.reg = theStackSlot; |
| codegen->bytecodeGenerator->addInstruction(store); |
| return; |
| } |
| case ScopedLocal: { |
| if (scope == 0) { |
| Instruction::StoreLocal store; |
| store.index = index; |
| codegen->bytecodeGenerator->addInstruction(store); |
| } else { |
| Instruction::StoreScopedLocal store; |
| store.index = index; |
| store.scope = scope; |
| codegen->bytecodeGenerator->addInstruction(store); |
| } |
| return; |
| } |
| case Name: { |
| Context *c = codegen->currentContext(); |
| if (c->isStrict) { |
| Instruction::StoreNameStrict store; |
| store.name = nameAsIndex(); |
| codegen->bytecodeGenerator->addInstruction(store); |
| } else { |
| Instruction::StoreNameSloppy store; |
| store.name = nameAsIndex(); |
| codegen->bytecodeGenerator->addInstruction(store); |
| } |
| } return; |
| case Member: |
| if (!disable_lookups && codegen->useFastLookups) { |
| Instruction::SetLookup store; |
| store.base = propertyBase.stackSlot(); |
| store.index = codegen->registerSetterLookup(propertyNameIndex); |
| codegen->bytecodeGenerator->addInstruction(store); |
| } else { |
| Instruction::StoreProperty store; |
| store.base = propertyBase.stackSlot(); |
| store.name = propertyNameIndex; |
| codegen->bytecodeGenerator->addInstruction(store); |
| } |
| return; |
| case Subscript: { |
| Instruction::StoreElement store; |
| store.base = elementBase; |
| store.index = elementSubscript.stackSlot(); |
| codegen->bytecodeGenerator->addInstruction(store); |
| } return; |
| case Invalid: |
| case Accumulator: |
| case Const: |
| case Import: |
| break; |
| } |
| |
| Q_UNREACHABLE(); |
| } |
| |
| void Codegen::Reference::loadInAccumulator() const |
| { |
| auto tdzCheck = [this](bool requiresCheck){ |
| if (!requiresCheck) |
| return; |
| Instruction::DeadTemporalZoneCheck check; |
| check.name = codegen->registerString(name); |
| codegen->bytecodeGenerator->addInstruction(check); |
| }; |
| auto tdzCheckStackSlot = [this, tdzCheck](Moth::StackSlot slot, bool requiresCheck){ |
| if (!requiresCheck) |
| return; |
| Instruction::LoadReg load; |
| load.reg = slot; |
| codegen->bytecodeGenerator->addInstruction(load); |
| tdzCheck(true); |
| }; |
| |
| switch (type) { |
| case Accumulator: |
| return; |
| case Super: |
| Q_UNREACHABLE(); |
| return; |
| case SuperProperty: |
| tdzCheckStackSlot(property, subscriptRequiresTDZCheck); |
| Instruction::LoadSuperProperty load; |
| load.property = property.stackSlot(); |
| codegen->bytecodeGenerator->addInstruction(load); |
| return; |
| case Const: { |
| QT_WARNING_PUSH |
| QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty structs. |
| if (constant == Encode::null()) { |
| Instruction::LoadNull load; |
| codegen->bytecodeGenerator->addInstruction(load); |
| } else if (constant == Encode(true)) { |
| Instruction::LoadTrue load; |
| codegen->bytecodeGenerator->addInstruction(load); |
| } else if (constant == Encode(false)) { |
| Instruction::LoadFalse load; |
| codegen->bytecodeGenerator->addInstruction(load); |
| } else if (constant == Encode::undefined()) { |
| Instruction::LoadUndefined load; |
| codegen->bytecodeGenerator->addInstruction(load); |
| } else { |
| StaticValue p = StaticValue::fromReturnedValue(constant); |
| if (p.isNumber()) { |
| double d = p.asDouble(); |
| int i = static_cast<int>(d); |
| if (d == i && (d != 0 || !std::signbit(d))) { |
| if (!i) { |
| Instruction::LoadZero load; |
| codegen->bytecodeGenerator->addInstruction(load); |
| return; |
| } |
| Instruction::LoadInt load; |
| load.value = StaticValue::fromReturnedValue(constant).toInt32(); |
| codegen->bytecodeGenerator->addInstruction(load); |
| return; |
| } |
| } |
| Instruction::LoadConst load; |
| load.index = codegen->registerConstant(constant); |
| codegen->bytecodeGenerator->addInstruction(load); |
| } |
| QT_WARNING_POP |
| } return; |
| case StackSlot: { |
| Instruction::LoadReg load; |
| load.reg = stackSlot(); |
| codegen->bytecodeGenerator->addInstruction(load); |
| tdzCheck(requiresTDZCheck); |
| } return; |
| case ScopedLocal: { |
| if (!scope) { |
| Instruction::LoadLocal load; |
| load.index = index; |
| codegen->bytecodeGenerator->addInstruction(load); |
| } else { |
| Instruction::LoadScopedLocal load; |
| load.index = index; |
| load.scope = scope; |
| codegen->bytecodeGenerator->addInstruction(load); |
| } |
| tdzCheck(requiresTDZCheck); |
| return; |
| } |
| case Name: |
| if (global) { |
| // these value properties of the global object are immutable, we we can directly convert them |
| // to their numeric value here |
| if (name == QStringLiteral("undefined")) { |
| Reference::fromConst(codegen, Encode::undefined()).loadInAccumulator(); |
| return; |
| } else if (name == QStringLiteral("Infinity")) { |
| Reference::fromConst(codegen, Encode(qInf())).loadInAccumulator(); |
| return; |
| } else if (name == QStringLiteral("Nan")) { |
| Reference::fromConst(codegen, Encode(qQNaN())).loadInAccumulator(); |
| return; |
| } |
| } |
| if (!disable_lookups && global) { |
| if (qmlGlobal) { |
| Instruction::LoadQmlContextPropertyLookup load; |
| load.index = codegen->registerQmlContextPropertyGetterLookup(nameAsIndex()); |
| codegen->bytecodeGenerator->addInstruction(load); |
| } else { |
| Instruction::LoadGlobalLookup load; |
| load.index = codegen->registerGlobalGetterLookup(nameAsIndex()); |
| codegen->bytecodeGenerator->addInstruction(load); |
| } |
| } else { |
| Instruction::LoadName load; |
| load.name = nameAsIndex(); |
| codegen->bytecodeGenerator->addInstruction(load); |
| } |
| return; |
| case Member: |
| propertyBase.loadInAccumulator(); |
| tdzCheck(requiresTDZCheck); |
| if (!disable_lookups && codegen->useFastLookups) { |
| Instruction::GetLookup load; |
| load.index = codegen->registerGetterLookup(propertyNameIndex); |
| codegen->bytecodeGenerator->addInstruction(load); |
| } else { |
| Instruction::LoadProperty load; |
| load.name = propertyNameIndex; |
| codegen->bytecodeGenerator->addInstruction(load); |
| } |
| return; |
| case Import: { |
| Instruction::LoadImport load; |
| load.index = index; |
| codegen->bytecodeGenerator->addInstruction(load); |
| tdzCheck(requiresTDZCheck); |
| } return; |
| case Subscript: { |
| tdzCheckStackSlot(elementBase, requiresTDZCheck); |
| elementSubscript.loadInAccumulator(); |
| tdzCheck(subscriptRequiresTDZCheck); |
| Instruction::LoadElement load; |
| load.base = elementBase; |
| codegen->bytecodeGenerator->addInstruction(load); |
| } return; |
| case Invalid: |
| break; |
| } |
| Q_UNREACHABLE(); |
| } |