blob: c43ea64e2e8e2dcc16008d6ad48a62a8e87c1f3f [file] [log] [blame]
/****************************************************************************
**
** 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::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;
}
} // 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.line = loc.startLine;
_error.column = loc.startColumn;
}
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(AST::SourceLocation(), QStringLiteral("Super lvalues not implemented."));
return *this;
case Member:
if (!propertyBase.isStackSlot()) {
Reference r = *this;
r.propertyBase = propertyBase.storeOnStack();
return r;
}
return *this;
case Subscript:
if (!elementSubscript.isStackSlot()) {
Reference r = *this;
r.elementSubscript = elementSubscript.storeOnStack();
return r;
}
return *this;
default:
return *this;
}
}
Codegen::Reference Codegen::Reference::storeConsumeAccumulator() const
{
storeAccumulator(); // it doesn't matter what happens here, just do it.
return Reference();
}
Codegen::Reference Codegen::Reference::baseObject() const
{
if (type == Reference::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();
}