aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2018-03-28 10:58:58 +0200
committerLars Knoll <lars.knoll@qt.io>2018-05-02 14:17:21 +0000
commit851c28d140c398990513640047a20aab36ccc655 (patch)
treedd9391f74c6a98a234cf2b5ac09bc41b7b7d3b98 /src
parent13cc936859518b5fa378c7b8242d56ebf49ebce9 (diff)
Refactor variable resolving
Move variable resolving into the context, and avoid creating ExecutionContext's whereever we can. This prepares things for block scoping, where this becomes rather important to be able to achieve decent performance. Change-Id: Idf3d3c12cf348a2c3da01989c26c8529ceb36c12 Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/qml/compiler/qv4codegen.cpp173
-rw-r--r--src/qml/compiler/qv4codegen_p.h9
-rw-r--r--src/qml/compiler/qv4compilercontext.cpp145
-rw-r--r--src/qml/compiler/qv4compilercontext_p.h51
-rw-r--r--src/qml/compiler/qv4compilerscanfunctions.cpp38
-rw-r--r--src/qml/jsruntime/qv4global_p.h5
6 files changed, 245 insertions, 176 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp
index 4df078ffc2..6a409edc58 100644
--- a/src/qml/compiler/qv4codegen.cpp
+++ b/src/qml/compiler/qv4codegen.cpp
@@ -272,9 +272,9 @@ void Codegen::statement(Statement *ast)
bytecodeGenerator->setLocation(ast->firstSourceLocation());
VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast);
- qSwap(_volataleMemoryLocations, vLocs);
+ qSwap(_volatileMemoryLocations, vLocs);
accept(ast);
- qSwap(_volataleMemoryLocations, vLocs);
+ qSwap(_volatileMemoryLocations, vLocs);
}
void Codegen::statement(ExpressionNode *ast)
@@ -287,11 +287,11 @@ void Codegen::statement(ExpressionNode *ast)
Result r(nx);
qSwap(_expr, r);
VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast);
- qSwap(_volataleMemoryLocations, vLocs);
+ qSwap(_volatileMemoryLocations, vLocs);
accept(ast);
- qSwap(_volataleMemoryLocations, vLocs);
+ qSwap(_volatileMemoryLocations, vLocs);
qSwap(_expr, r);
if (hasError)
@@ -1724,82 +1724,29 @@ bool Codegen::visit(FunctionExpression *ast)
Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs)
{
- int scope = 0;
- Context *c = _context;
-
- // skip the innermost context if it's simple (as the runtime won't
- // create a context for it
- if (c->canUseSimpleCall()) {
- Context::Member m = c->findMember(name);
- if (m.type != Context::UndefinedMember) {
- Q_ASSERT((!m.canEscape));
- Reference r = Reference::fromStackSlot(this, m.index, true /*isLocal*/);
- if (name == QLatin1String("arguments") || name == QLatin1String("eval")) {
- r.isArgOrEval = true;
- if (isLhs && c->isStrict)
- // ### add correct source location
- throwSyntaxError(SourceLocation(), QStringLiteral("Variable name may not be eval or arguments in strict mode"));
- }
- return r;
- }
- const int argIdx = c->findArgument(name);
- if (argIdx != -1) {
- Q_ASSERT(!c->argumentsCanEscape && (c->usesArgumentsObject != Context::ArgumentsObjectUsed || c->isStrict));
- return Reference::fromArgument(this, argIdx, _volataleMemoryLocations.isVolatile(name));
- }
- c = c->parent;
- }
-
- while (c->parent) {
- if (c->forceLookupByName())
- goto loadByName;
-
- Context::Member m = c->findMember(name);
- if (m.type != Context::UndefinedMember) {
- Reference r = m.canEscape ? Reference::fromScopedLocal(this, m.index, scope)
- : Reference::fromStackSlot(this, m.index, true /*isLocal*/);
- if (name == QLatin1String("arguments") || name == QLatin1String("eval")) {
- r.isArgOrEval = true;
- if (isLhs && c->isStrict)
- // ### add correct source location
- throwSyntaxError(SourceLocation(), QStringLiteral("Variable name may not be eval or arguments in strict mode"));
- }
- return r;
- }
- const int argIdx = c->findArgument(name);
- if (argIdx != -1) {
- if (c->argumentsCanEscape || c->usesArgumentsObject == Context::ArgumentsObjectUsed) {
- int idx = argIdx + c->locals.size();
- return Reference::fromScopedLocal(this, idx, scope);
- } else {
- Q_ASSERT(scope == 0);
- return Reference::fromArgument(this, argIdx, _volataleMemoryLocations.isVolatile(name));
- }
- }
-
- if (!c->isStrict && c->hasDirectEval)
- goto loadByName;
-
- ++scope;
- c = c->parent;
- }
-
- {
- // This hook allows implementing QML lookup semantics
- Reference fallback = fallbackNameLookup(name);
- if (fallback.type != Reference::Invalid)
- return fallback;
- }
-
- if (!c->parent && !c->forceLookupByName() && _context->type != ContextType::Eval && c->type != ContextType::Binding) {
- Reference r = Reference::fromName(this, name);
- r.global = true;
+ Context::ResolvedName resolved = _context->resolveName(name);
+
+ if (resolved.type == Context::ResolvedName::Local || resolved.type == Context::ResolvedName::Stack) {
+ if (resolved.isArgOrEval && isLhs)
+ // ### add correct source location
+ throwSyntaxError(SourceLocation(), QStringLiteral("Variable name may not be eval or arguments in strict mode"));
+ Reference r = (resolved.type == Context::ResolvedName::Local) ?
+ Reference::fromScopedLocal(this, resolved.index, resolved.scope) :
+ Reference::fromStackSlot(this, resolved.index, true /*isLocal*/);
+ if (r.isStackSlot() && _volatileMemoryLocations.isVolatile(name))
+ r.isVolatile = true;
+ r.isArgOrEval = resolved.isArgOrEval;
return r;
}
- // global context or with. Lookup by name
- loadByName:
- return Reference::fromName(this, name);
+ // This hook allows implementing QML lookup semantics
+ Reference fallback = fallbackNameLookup(name);
+ if (fallback.type != Reference::Invalid)
+ return fallback;
+
+ Reference r = Reference::fromName(this, name);
+ r.global = (resolved.type == Context::ResolvedName::Global);
+ return r;
}
void Codegen::loadClosure(int closureId)
@@ -2365,60 +2312,14 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast,
bytecodeGenerator->newRegisterArray(sizeof(CallData)/sizeof(Value) - 1 + _context->arguments.size());
int returnAddress = -1;
- bool _requiresReturnValue = (_context->type == ContextType::Binding || _context->type == ContextType::Eval || _context->type == ContextType::Global);
+ bool _requiresReturnValue = _context->requiresImplicitReturnValue();
qSwap(requiresReturnValue, _requiresReturnValue);
if (requiresReturnValue)
returnAddress = bytecodeGenerator->newRegister();
- if (!_context->parent || _context->usesArgumentsObject == Context::ArgumentsObjectUnknown)
- _context->usesArgumentsObject = Context::ArgumentsObjectNotUsed;
- if (_context->usesArgumentsObject == Context::ArgumentsObjectUsed)
- _context->addLocalVar(QStringLiteral("arguments"), Context::VariableDeclaration, AST::VariableScope::Var);
-
- bool allVarsEscape = _context->hasWith || _context->hasTry || _context->hasDirectEval;
- if (_context->type == ContextType::Binding // we don't really need this for bindings, but we do for signal handlers, and we don't know if the code is a signal handler or not.
- || (!_context->canUseSimpleCall() && _context->type != ContextType::Global &&
- (_context->type != ContextType::Eval || _context->isStrict))) {
- Instruction::CreateCallContext createContext;
- bytecodeGenerator->addInstruction(createContext);
- }
-
- // variables in global code are properties of the global context object, not locals as with other functions.
- if (_context->type == ContextType::Function || _context->type == ContextType::Binding) {
- for (Context::MemberMap::iterator it = _context->members.begin(), end = _context->members.end(); it != end; ++it) {
- const QString &local = it.key();
- if (allVarsEscape)
- it->canEscape = true;
- if (it->canEscape) {
- it->index = _context->locals.size();
- _context->locals.append(local);
- if (it->type == Context::ThisFunctionName) {
- // move the name from the stack to the call context
- Instruction::LoadReg load;
- load.reg = CallData::Function;
- bytecodeGenerator->addInstruction(load);
- Instruction::StoreLocal store;
- store.index = it->index;
- bytecodeGenerator->addInstruction(store);
- }
- } else {
- if (it->type == Context::ThisFunctionName)
- it->index = CallData::Function;
- else
- it->index = bytecodeGenerator->newRegister();
- }
- }
- } else {
- for (Context::MemberMap::const_iterator it = _context->members.constBegin(), cend = _context->members.constEnd(); it != cend; ++it) {
- const QString &local = it.key();
+ qSwap(_returnAddress, returnAddress);
- Instruction::DeclareVar declareVar;
- declareVar.isDeletable = false;
- declareVar.varName = registerString(local);
- bytecodeGenerator->addInstruction(declareVar);
- }
- }
+ _context->emitHeaderBytecode(this);
- qSwap(_returnAddress, returnAddress);
for (const Context::Member &member : qAsConst(_context->members)) {
if (member.function) {
const int function = defineFunction(member.function->name.toString(), member.function, member.function->formals,
@@ -2434,21 +2335,6 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast,
}
}
}
- if (_context->usesArgumentsObject == Context::ArgumentsObjectUsed) {
- if (_context->isStrict || (formals && !formals->isSimpleParameterList())) {
- Instruction::CreateUnmappedArgumentsObject setup;
- bytecodeGenerator->addInstruction(setup);
- } else {
- Instruction::CreateMappedArgumentsObject setup;
- bytecodeGenerator->addInstruction(setup);
- }
- referenceForName(QStringLiteral("arguments"), false).storeConsumeAccumulator();
- }
- if (_context->usesThis && !_context->isStrict) {
- // make sure we convert this to an object
- Instruction::ConvertThisToObject convert;
- bytecodeGenerator->addInstruction(convert);
- }
int argc = 0;
while (formals) {
@@ -2468,7 +2354,8 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast,
bytecodeGenerator->addInstruction(rest);
arg.storeConsumeAccumulator();
} else {
- initializeAndDestructureBindingElement(e, arg);
+ if (e->bindingPattern || e->initializer)
+ initializeAndDestructureBindingElement(e, arg);
}
formals = formals->next;
++argc;
@@ -3087,7 +2974,7 @@ bool Codegen::visit(WithStatement *ast)
RegisterScope scope(this);
- _context->hasWith = true;
+ Q_ASSERT(_context->hasWith);
Reference src = expression(ast->expression);
if (hasError)
diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h
index 03579d4b29..10926dd30b 100644
--- a/src/qml/compiler/qv4codegen_p.h
+++ b/src/qml/compiler/qv4codegen_p.h
@@ -484,6 +484,7 @@ protected:
void addCJump();
+public:
int registerString(const QString &name) {
return jsUnitGenerator->registerString(name);
}
@@ -492,6 +493,7 @@ protected:
int registerSetterLookup(int nameIndex) { return jsUnitGenerator->registerSetterLookup(nameIndex); }
int registerGlobalGetterLookup(int nameIndex) { return jsUnitGenerator->registerGlobalGetterLookup(nameIndex); }
+protected:
// Returns index in _module->functions
virtual int defineFunction(const QString &name, AST::Node *ast,
AST::FormalParameterList *formals,
@@ -515,8 +517,6 @@ protected:
void destructurePropertyList(const Reference &object, AST::PatternPropertyList *bindingList);
void destructureElementList(const Reference &array, AST::PatternElementList *bindingList);
- Reference referenceForName(const QString &name, bool lhs);
-
void loadClosure(int index);
// Hook provided to implement QML lookup semantics
@@ -645,10 +645,13 @@ public:
void handleTryCatch(AST::TryStatement *ast);
void handleTryFinally(AST::TryStatement *ast);
+ Reference referenceForName(const QString &name, bool lhs);
+
QQmlRefPointer<QV4::CompiledData::CompilationUnit> generateCompilationUnit(bool generateUnitData = true);
static QQmlRefPointer<QV4::CompiledData::CompilationUnit> createUnitForLoading();
Context *currentContext() const { return _context; }
+ BytecodeGenerator *generator() const { return bytecodeGenerator; }
protected:
friend class ScanFunctions;
@@ -656,7 +659,7 @@ protected:
friend struct ControlFlowCatch;
friend struct ControlFlowFinally;
Result _expr;
- VolatileMemoryLocations _volataleMemoryLocations;
+ VolatileMemoryLocations _volatileMemoryLocations;
Module *_module;
int _returnAddress;
Context *_context;
diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp
index 61b9254681..1c583b5cf2 100644
--- a/src/qml/compiler/qv4compilercontext.cpp
+++ b/src/qml/compiler/qv4compilercontext.cpp
@@ -39,6 +39,7 @@
#include "qv4compilercontext_p.h"
#include "qv4compilercontrolflow_p.h"
+#include "qv4bytecodegenerator_p.h"
QT_USE_NAMESPACE
using namespace QV4;
@@ -81,4 +82,148 @@ bool Context::forceLookupByName()
return false;
}
+bool Context::addLocalVar(const QString &name, Context::MemberType type, VariableScope scope, FunctionExpression *function)
+{
+ if (name.isEmpty())
+ return true;
+
+ if (type != FunctionDefinition) {
+ if (formals && formals->containsName(name))
+ return (scope == QQmlJS::AST::VariableScope::Var);
+ }
+ MemberMap::iterator it = members.find(name);
+ if (it != members.end()) {
+ if (scope != QQmlJS::AST::VariableScope::Var || (*it).scope != QQmlJS::AST::VariableScope::Var)
+ return false;
+ if ((*it).type <= type) {
+ (*it).type = type;
+ (*it).function = function;
+ }
+ return true;
+ }
+ Member m;
+ m.type = type;
+ m.function = function;
+ m.scope = scope;
+ members.insert(name, m);
+ return true;
+}
+
+Context::ResolvedName Context::resolveName(const QString &name)
+{
+ int scope = 0;
+ Context *c = this;
+
+ ResolvedName result;
+
+ while (c->parent) {
+ if (c->forceLookupByName())
+ return result;
+
+ Context::Member m = c->findMember(name);
+ if (m.type != Context::UndefinedMember) {
+ result.type = m.canEscape ? ResolvedName::Local : ResolvedName::Stack;
+ result.scope = scope;
+ result.index = m.index;
+ if (c->isStrict && (name == QLatin1String("arguments") || name == QLatin1String("eval")))
+ result.isArgOrEval = true;
+ return result;
+ Q_ASSERT(result.type != ResolvedName::Stack || result.scope == 0);
+ }
+ const int argIdx = c->findArgument(name);
+ if (argIdx != -1) {
+ if (c->argumentsCanEscape) {
+ result.index = argIdx + c->locals.size();
+ result.scope = scope;
+ result.type = ResolvedName::Local;
+ return result;
+ } else {
+ Q_ASSERT(scope == 0);
+ result.index = argIdx + sizeof(CallData)/sizeof(Value) - 1;
+ result.scope = 0;
+ result.type = ResolvedName::Stack;
+ return result;
+ }
+ }
+ if (!c->isStrict && c->hasDirectEval)
+ return result;
+
+ if (c->requiresExecutionContext)
+ ++scope;
+ c = c->parent;
+ }
+
+ // ### can we relax the restrictions here?
+ if (c->forceLookupByName() || type == ContextType::Eval || c->type == ContextType::Binding)
+ return result;
+
+ result.type = ResolvedName::Global;
+ return result;
+}
+
+void Context::emitHeaderBytecode(Codegen *codegen)
+{
+ using Instruction = Moth::Instruction;
+ Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator();
+
+ bool allVarsEscape = hasWith || hasTry || hasDirectEval;
+ if (requiresExecutionContext ||
+ type == ContextType::Binding) { // we don't really need this for bindings, but we do for signal handlers, and we don't know if the code is a signal handler or not.
+ Instruction::CreateCallContext createContext;
+ bytecodeGenerator->addInstruction(createContext);
+ }
+ if (usesThis && !isStrict) {
+ // make sure we convert this to an object
+ Instruction::ConvertThisToObject convert;
+ bytecodeGenerator->addInstruction(convert);
+ }
+
+ // variables in global code are properties of the global context object, not locals as with other functions.
+ if (type == ContextType::Function || type == ContextType::Binding) {
+ for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) {
+ const QString &local = it.key();
+ if (allVarsEscape)
+ it->canEscape = true;
+ if (it->canEscape) {
+ it->index = locals.size();
+ locals.append(local);
+ if (it->type == Context::ThisFunctionName) {
+ // move the name from the stack to the call context
+ Instruction::LoadReg load;
+ load.reg = CallData::Function;
+ bytecodeGenerator->addInstruction(load);
+ Instruction::StoreLocal store;
+ store.index = it->index;
+ bytecodeGenerator->addInstruction(store);
+ }
+ } else {
+ if (it->type == Context::ThisFunctionName)
+ it->index = CallData::Function;
+ else
+ it->index = bytecodeGenerator->newRegister();
+ }
+ }
+ } else {
+ for (Context::MemberMap::const_iterator it = members.constBegin(), cend = members.constEnd(); it != cend; ++it) {
+ const QString &local = it.key();
+
+ Instruction::DeclareVar declareVar;
+ declareVar.isDeletable = false;
+ declareVar.varName = codegen->registerString(local);
+ bytecodeGenerator->addInstruction(declareVar);
+ }
+ }
+
+ if (usesArgumentsObject == Context::ArgumentsObjectUsed) {
+ if (isStrict || (formals && !formals->isSimpleParameterList())) {
+ Instruction::CreateUnmappedArgumentsObject setup;
+ bytecodeGenerator->addInstruction(setup);
+ } else {
+ Instruction::CreateMappedArgumentsObject setup;
+ bytecodeGenerator->addInstruction(setup);
+ }
+ codegen->referenceForName(QStringLiteral("arguments"), false).storeConsumeAccumulator();
+ }
+}
+
QT_END_NAMESPACE
diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h
index 2864d759f9..1047af973d 100644
--- a/src/qml/compiler/qv4compilercontext_p.h
+++ b/src/qml/compiler/qv4compilercontext_p.h
@@ -149,6 +149,7 @@ struct Context {
bool hasWith = false;
bool returnsClosure = false;
mutable bool argumentsCanEscape = false;
+ bool requiresExecutionContext = false;
enum UsesArgumentsObject {
ArgumentsObjectUnknown,
@@ -217,7 +218,6 @@ struct Context {
bool forceLookupByName();
-
bool canUseSimpleCall() const {
return nestedContexts.isEmpty() &&
locals.isEmpty() &&
@@ -256,36 +256,33 @@ struct Context {
return true;
}
+ bool requiresImplicitReturnValue() const {
+ return type == ContextType::Binding ||
+ type == ContextType::Eval ||
+ type == ContextType::Global;
+ }
+
void addUsedVariable(const QString &name) {
usedVariables.insert(name);
}
- bool addLocalVar(const QString &name, MemberType type, QQmlJS::AST::VariableScope scope, QQmlJS::AST::FunctionExpression *function = nullptr)
- {
- if (name.isEmpty())
- return true;
-
- if (type != FunctionDefinition) {
- if (formals && formals->containsName(name))
- return (scope == QQmlJS::AST::VariableScope::Var);
- }
- MemberMap::iterator it = members.find(name);
- if (it != members.end()) {
- if (scope != QQmlJS::AST::VariableScope::Var || (*it).scope != QQmlJS::AST::VariableScope::Var)
- return false;
- if ((*it).type <= type) {
- (*it).type = type;
- (*it).function = function;
- }
- return true;
- }
- Member m;
- m.type = type;
- m.function = function;
- m.scope = scope;
- members.insert(name, m);
- return true;
- }
+ bool addLocalVar(const QString &name, MemberType type, QQmlJS::AST::VariableScope scope, QQmlJS::AST::FunctionExpression *function = nullptr);
+
+ struct ResolvedName {
+ enum Type {
+ Unresolved,
+ Global,
+ Local,
+ Stack
+ };
+ Type type = Unresolved;
+ bool isArgOrEval = false;
+ int scope = -1;
+ int index = -1;
+ bool isValid() const { return type != Unresolved; }
+ };
+ ResolvedName resolveName(const QString &name);
+ void emitHeaderBytecode(Compiler::Codegen *codegen);
};
diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp
index b779e37946..0feba7bb61 100644
--- a/src/qml/compiler/qv4compilerscanfunctions.cpp
+++ b/src/qml/compiler/qv4compilerscanfunctions.cpp
@@ -474,13 +474,17 @@ void ScanFunctions::calcEscapingVariables()
while (c) {
Context::MemberMap::const_iterator it = c->members.find(var);
if (it != c->members.end()) {
- if (c != inner)
+ if (c != inner) {
it->canEscape = true;
+ c->requiresExecutionContext = true;
+ }
break;
}
if (c->findArgument(var) != -1) {
- if (c != inner)
+ if (c != inner) {
c->argumentsCanEscape = true;
+ c->requiresExecutionContext = true;
+ }
break;
}
c = c->parent;
@@ -489,16 +493,44 @@ void ScanFunctions::calcEscapingVariables()
Context *c = inner->parent;
while (c) {
c->hasDirectEval |= inner->hasDirectEval;
+ c->hasWith |= inner->hasWith;
c = c->parent;
}
}
+ for (Context *c : qAsConst(m->contextMap)) {
+ bool allVarsEscape = c->hasWith || c->hasTry || c->hasDirectEval || m->debugMode;
+ if (allVarsEscape) {
+ c->requiresExecutionContext = true;
+ c->argumentsCanEscape = true;
+ for (auto &m : c->members) {
+ m.canEscape = true;
+ }
+ }
+ // ### for now until we have lexically scoped vars that'll require it
+ if (c->type == ContextType::Global)
+ c->requiresExecutionContext = false;
+ // ### Shouldn't be required, we could probably rather change the ContextType to FunctionCode for strict eval
+ if (c->type == ContextType::Eval && c->isStrict)
+ c->requiresExecutionContext = true;
+ if (!c->parent || c->usesArgumentsObject == Context::ArgumentsObjectUnknown)
+ c->usesArgumentsObject = Context::ArgumentsObjectNotUsed;
+ if (c->usesArgumentsObject == Context::ArgumentsObjectUsed) {
+ QString arguments = QStringLiteral("arguments");
+ c->addLocalVar(arguments, Context::VariableDeclaration, AST::VariableScope::Var);
+ if (!c->isStrict) {
+ c->argumentsCanEscape = true;
+ c->requiresExecutionContext = true;
+ }
+ }
+ }
static const bool showEscapingVars = qEnvironmentVariableIsSet("QV4_SHOW_ESCAPING_VARS");
if (showEscapingVars) {
qDebug() << "==== escaping variables ====";
for (Context *c : qAsConst(m->contextMap)) {
qDebug() << "Context" << c->name << ":";
- qDebug() << " Arguments escape" << c->argumentsCanEscape;
+ if (c->argumentsCanEscape)
+ qDebug() << " Arguments escape";
for (auto it = c->members.constBegin(); it != c->members.constEnd(); ++it) {
qDebug() << " " << it.key() << it.value().canEscape;
}
diff --git a/src/qml/jsruntime/qv4global_p.h b/src/qml/jsruntime/qv4global_p.h
index 6cf1e7d78a..d9660869f4 100644
--- a/src/qml/jsruntime/qv4global_p.h
+++ b/src/qml/jsruntime/qv4global_p.h
@@ -153,6 +153,11 @@ namespace Compiler {
struct Module;
struct Context;
struct JSUnitGenerator;
+ class Codegen;
+}
+
+namespace Moth {
+ class BytecodeGenerator;
}
namespace Heap {