// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qv4vme_moth_p.h"

#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonobject.h>

#include <private/qv4alloca_p.h>
#include <private/qv4instr_moth_p.h>
#include <private/qv4value_p.h>
#include <private/qv4debugging_p.h>
#include <private/qv4function_p.h>
#include <private/qv4functionobject_p.h>
#include <private/qv4math_p.h>
#include <private/qv4scopedvalue_p.h>
#include <private/qv4lookup_p.h>
#include <private/qv4regexp_p.h>
#include <private/qv4regexpobject_p.h>
#include <private/qv4string_p.h>
#include <private/qv4profiling_p.h>
#include <private/qv4jscall_p.h>
#include <private/qv4generatorobject_p.h>
#include <private/qv4alloca_p.h>
#include <private/qqmljavascriptexpression_p.h>
#include <private/qv4qmlcontext_p.h>
#include <QtQml/private/qv4runtime_p.h>
#include <iostream>

#if QT_CONFIG(qml_jit)
#include <private/qv4baselinejit_p.h>
#endif

#include <qtqml_tracepoints_p.h>

#undef COUNT_INSTRUCTIONS

Q_TRACE_POINT(qtqml, QQmlV4_function_call_entry, const QV4::ExecutionEngine *engine, const QString &function, const QString &fileName, int line, int column)
Q_TRACE_POINT(qtqml, QQmlV4_function_call_exit)

enum { ShowWhenDeoptimiationHappens = 0 };

extern "C" {

// This is the interface to Qt Creator's (new) QML debugger.

/*! \internal
    \since 5.5

    This function is called uncondionally from VME::run().

    An attached debugger can set a breakpoint here to
    intercept calls to VME::run().
 */

Q_QML_EXPORT void qt_v4ResolvePendingBreakpointsHook()
{
}

/*! \internal
    \since 5.5

    This function is called when a QML interpreter breakpoint
    is hit.

    An attached debugger can set a breakpoint here.
*/
Q_QML_EXPORT void qt_v4TriggeredBreakpointHook()
{
}

/*! \internal
    \since 5.5

    The main entry point into "Native Mixed" Debugging.

    Commands are passed as UTF-8 encoded JSON data.
    The data has two compulsory fields:
    \list
    \li \c version: Version of the protocol (currently 1)
    \li \c command: Name of the command
    \endlist

    Depending on \c command, more fields can be present.

    Error is indicated by negative return values,
    success by non-negative return values.

    \c protocolVersion:
    Returns version of implemented protocol.

    \c insertBreakpoint:
    Sets a breakpoint on a given file and line.
    \list
    \li \c fullName: Name of the QML/JS file
    \li \c lineNumber: Line number in the file
    \li \c condition: Breakpoint condition
    \endlist
    Returns a unique positive number as handle.

    \c removeBreakpoint:
    Removes a breakpoint from a given file and line.
    \list
    \li \c fullName: Name of the QML/JS file
    \li \c lineNumber: Line number in the file
    \li \c condition: Breakpoint condition
    \endlist
    Returns zero on success, a negative number on failure.

    \c prepareStep:
    Puts the interpreter in stepping mode.
    Returns zero.

*/
Q_QML_EXPORT int qt_v4DebuggerHook(const char *json);


} // extern "C"

#if QT_CONFIG(qml_debug)
static int qt_v4BreakpointCount = 0;
static bool qt_v4IsDebugging = false;
static bool qt_v4IsStepping = false;

namespace {
class Breakpoint
{
public:
    Breakpoint() : bpNumber(0), lineNumber(-1) {}

    bool matches(const QString &file, int line) const
    {
        return fullName == file && lineNumber == line;
    }

    int bpNumber;
    int lineNumber;
    QString fullName;      // e.g. /opt/project/main.qml
    QString engineName;    // e.g. qrc:/main.qml
    QString condition;     // optional
};

QVector<Breakpoint> qt_v4Breakpoints;
Breakpoint qt_v4LastStop;

void qt_v4TriggerBreakpoint(const Breakpoint &bp, QV4::Function *function)
{
    qt_v4LastStop = bp;

    // Set up some auxiliary data for informational purpose.
    // This is not part of the protocol.
    QV4::Heap::String *functionName = function->name();
    QByteArray functionNameUtf8;
    if (functionName)
        functionNameUtf8 = functionName->toQString().toUtf8();

    qt_v4TriggeredBreakpointHook(); // Trigger Breakpoint.
}

}

int qt_v4DebuggerHook(const char *json)
{
    const int ProtocolVersion = 1;

    enum {
        Success = 0,
        WrongProtocol,
        NoSuchCommand,
        NoSuchBreakpoint
    };

    QJsonDocument doc = QJsonDocument::fromJson(json);
    QJsonObject ob = doc.object();
    QByteArray command = ob.value(QLatin1String("command")).toString().toUtf8();

    if (command == "protocolVersion") {
        return ProtocolVersion; // Version number.
    }

    int version = ob.value(QLatin1String("version")).toString().toInt();
    if (version != ProtocolVersion) {
        return -WrongProtocol;
    }

    if (command == "insertBreakpoint") {
        Breakpoint bp;
        bp.bpNumber = ++qt_v4BreakpointCount;
        bp.lineNumber = ob.value(QLatin1String("lineNumber")).toString().toInt();
        bp.engineName = ob.value(QLatin1String("engineName")).toString();
        bp.fullName = ob.value(QLatin1String("fullName")).toString();
        bp.condition = ob.value(QLatin1String("condition")).toString();
        qt_v4Breakpoints.append(bp);
        qt_v4IsDebugging = true;
        return bp.bpNumber;
    }

    if (command == "removeBreakpoint") {
        int lineNumber = ob.value(QLatin1String("lineNumber")).toString().toInt();
        QString fullName = ob.value(QLatin1String("fullName")).toString();
        if (qt_v4Breakpoints.last().matches(fullName, lineNumber)) {
            qt_v4Breakpoints.removeLast();
            qt_v4IsDebugging = !qt_v4Breakpoints.isEmpty();
            return Success;
        }
        for (int i = 0; i + 1 < qt_v4Breakpoints.size(); ++i) {
            if (qt_v4Breakpoints.at(i).matches(fullName, lineNumber)) {
                qt_v4Breakpoints[i] = qt_v4Breakpoints.takeLast();
                return Success; // Ok.
            }
        }
        return -NoSuchBreakpoint; // Failure
    }

    if (command == "prepareStep") {
        qt_v4IsStepping = true;
        return Success; // Ok.
    }


    return -NoSuchCommand; // Failure.
}

Q_NEVER_INLINE static void qt_v4CheckForBreak(QV4::CppStackFrame *frame)
{
    if (!qt_v4IsStepping && !qt_v4Breakpoints.size())
        return;

    const int lineNumber = frame->lineNumber();
    QV4::Function *function = frame->v4Function;
    QString engineName = function->sourceFile();

    if (engineName.isEmpty())
        return;

    if (qt_v4IsStepping) {
        if (qt_v4LastStop.lineNumber != lineNumber
                || qt_v4LastStop.engineName != engineName) {
            qt_v4IsStepping = false;
            Breakpoint bp;
            bp.bpNumber = 0;
            bp.lineNumber = lineNumber;
            bp.engineName = engineName;
            qt_v4TriggerBreakpoint(bp, function);
            return;
        }
    }

    for (int i = qt_v4Breakpoints.size(); --i >= 0; ) {
        const Breakpoint &bp = qt_v4Breakpoints.at(i);
        if (bp.lineNumber != lineNumber)
            continue;
        if (bp.engineName != engineName)
            continue;

        qt_v4TriggerBreakpoint(bp, function);
    }
}

Q_NEVER_INLINE static void debug_slowPath(QV4::ExecutionEngine *engine)
{
    QV4::Debugging::Debugger *debugger = engine->debugger();
    if (debugger && debugger->pauseAtNextOpportunity())
        debugger->maybeBreakAtInstruction();
    if (qt_v4IsDebugging)
        qt_v4CheckForBreak(engine->currentStackFrame);
}

#endif // QT_CONFIG(qml_debug)
// End of debugger interface

using namespace QV4;
using namespace QV4::Moth;

#ifdef COUNT_INSTRUCTIONS
static struct InstrCount {
    InstrCount() {
        fprintf(stderr, "Counting instructions...\n");
        for (int i = 0; i < MOTH_NUM_INSTRUCTIONS(); ++i)
            hits[i] = 0;
    }
    ~InstrCount() {
        fprintf(stderr, "Instruction count:\n");
#define BLAH(I) \
        fprintf(stderr, "%llu : %s\n", hits[int(Instr::Type::I)], #I);
        FOR_EACH_MOTH_INSTR(BLAH)
        #undef BLAH
    }
    quint64 hits[MOTH_NUM_INSTRUCTIONS()];
    void hit(Instr::Type i) { hits[int(i)]++; }
} instrCount;
#endif // COUNT_INSTRUCTIONS

#define MOTH_BEGIN_INSTR_COMMON(instr) \
    { \
        INSTR_##instr(MOTH_DECODE)

#ifdef COUNT_INSTRUCTIONS
#  define MOTH_BEGIN_INSTR(instr) \
    MOTH_BEGIN_INSTR_COMMON(instr) \
    instrCount.hit(Instr::Type::instr);
#else // !COUNT_INSTRUCTIONS
#  define MOTH_BEGIN_INSTR(instr) \
    MOTH_BEGIN_INSTR_COMMON(instr)
#endif // COUNT_INSTRUCTIONS

#ifdef MOTH_COMPUTED_GOTO
#define MOTH_END_INSTR(instr) \
        MOTH_DISPATCH_SINGLE() \
    }
#else // !MOTH_COMPUTED_GOTO
#define MOTH_END_INSTR(instr) \
        continue; \
    }
#endif

static inline QV4::Value &stackValue(QV4::Value *stack, size_t slot, const JSTypesStackFrame *frame)
{
    Q_ASSERT(slot < CallData::HeaderSize() / sizeof(QV4::StaticValue)
                    + frame->jsFrame->argc()
                    + frame->v4Function->compiledFunction->nRegisters);
    Q_UNUSED(frame);

    return stack[slot];
}

#define STACK_VALUE(temp) stackValue(stack, temp, frame)

// qv4scopedvalue_p.h also defines a CHECK_EXCEPTION macro
#ifdef CHECK_EXCEPTION
#undef CHECK_EXCEPTION
#endif
#define CHECK_EXCEPTION \
    if (engine->hasException || engine->isInterrupted.loadRelaxed()) \
        goto handleUnwind

static inline Heap::CallContext *getScope(QV4::Value *stack, int level)
{
    Heap::ExecutionContext *scope = static_cast<ExecutionContext &>(stack[CallData::Context]).d();
    while (level > 0) {
        --level;
        scope = scope->outer;
    }
    Q_ASSERT(scope);
    return static_cast<Heap::CallContext *>(scope);
}

static inline const QV4::Value &constant(Function *function, int index)
{
    return function->compilationUnit->constants[index].asValue<QV4::Value>();
}

static bool compareEqualInt(QV4::Value &accumulator, QV4::Value lhs, int rhs)
{
  redo:
    if (lhs.isUndefined())
        return false;
    if (lhs.isManagedOrUndefined()) {
        // LHS: Managed
        if (lhs.m()->internalClass->vtable->isString)
            return RuntimeHelpers::stringToNumber(static_cast<String &>(lhs).toQString()) == rhs;
        accumulator = lhs;
        lhs = QV4::Value::fromReturnedValue(RuntimeHelpers::objectDefaultValue(&static_cast<QV4::Object &>(accumulator), PREFERREDTYPE_HINT));
        goto redo;
    }

    switch (lhs.quickType()) {
    case QV4::Value::QT_Empty:
        Q_UNREACHABLE();
    case QV4::Value::QT_Null:
        return false;
    case QV4::Value::QT_Bool:
    case QV4::Value::QT_Int:
        return lhs.int_32() == rhs;
    default: // double
        return lhs.doubleValue() == rhs;
    }
}

#define STORE_IP() frame->instructionPointer = int(code - function->codeData);
#define STORE_ACC() accumulator = acc;
#define ACC Value::fromReturnedValue(acc)
#define VALUE_TO_INT(i, val) \
    int i; \
    do { \
        if (Q_LIKELY(val.integerCompatible())) { \
            i = val.int_32(); \
        } else { \
            double d; \
            if (val.isDouble()) \
                d = val.doubleValue(); \
            else { \
                STORE_ACC(); \
                d = val.toNumberImpl(); \
                CHECK_EXCEPTION; \
            } \
            i = QJSNumberCoercion::toInteger(d); \
        } \
    } while (false)

namespace {
struct AOTCompiledMetaMethod
{
public:
    AOTCompiledMetaMethod(const Function::AOTCompiledFunction *aotCompiledFunction)
        : aotCompiledFunction(aotCompiledFunction)
    {}

    int parameterCount() const { return aotCompiledFunction->types.size() - 1; }
    QMetaType returnMetaType() const { return aotCompiledFunction->types[0]; }
    QMetaType parameterMetaType(int i) const { return aotCompiledFunction->types[i + 1]; }

private:
    const Function::AOTCompiledFunction *aotCompiledFunction = nullptr;
};
}

void VME::exec(MetaTypesStackFrame *frame, ExecutionEngine *engine)
{
    qt_v4ResolvePendingBreakpointsHook();
    if (engine->checkStackLimits()) {
        frame->setReturnValueUndefined();
        return;
    }
    ExecutionEngineCallDepthRecorder executionEngineCallDepthRecorder(engine);

    Function *function = frame->v4Function;
    Q_ASSERT(function->aotCompiledCode);
    Q_TRACE_SCOPE(QQmlV4_function_call, engine, function->name()->toQString(),
                  function->executableCompilationUnit()->fileName(),
                  function->compiledFunction->location.line(),
                  function->compiledFunction->location.column());
    Profiling::FunctionCallProfiler profiler(engine, function); // start execution profiling

    const AOTCompiledMetaMethod method(&function->aotCompiledFunction);
    QV4::coerceAndCall(
            engine, &method, frame->returnAndArgValues(),
            frame->returnAndArgTypes(), frame->argc(),
            [frame, engine, function](void **argv, int argc) {
        Q_UNUSED(argc);

        QQmlPrivate::AOTCompiledContext aotContext;
        if (auto context = QV4::ExecutionEngine::qmlContext(frame->context()->d())) {
            QV4::Heap::QQmlContextWrapper *wrapper = static_cast<Heap::QmlContext *>(context)->qml();
            aotContext.qmlScopeObject = wrapper->scopeObject;
            aotContext.qmlContext = wrapper->context;
        }

        aotContext.engine = engine->jsEngine();
        aotContext.compilationUnit = function->executableCompilationUnit();
        function->aotCompiledCode(&aotContext, argv);
    });
}

ReturnedValue VME::exec(JSTypesStackFrame *frame, ExecutionEngine *engine)
{
    qt_v4ResolvePendingBreakpointsHook();
    CHECK_STACK_LIMITS(engine);

    Function *function = frame->v4Function;
    Q_TRACE_SCOPE(QQmlV4_function_call, engine, function->name()->toQString(),
                  function->executableCompilationUnit()->fileName(),
                  function->compiledFunction->location.line(),
                  function->compiledFunction->location.column());
    Profiling::FunctionCallProfiler profiler(engine, function); // start execution profiling
    QV4::Debugging::Debugger *debugger = engine->debugger();

#if QT_CONFIG(qml_jit)
    if (debugger == nullptr) {
        // Check for codeRef here. In rare cases the JIT compilation may fail, which leaves us
        // with a (useless) codeRef, but no jittedCode. In that case, don't try to JIT again every
        // time we execute the function, but just interpret instead.
        if (function->codeRef == nullptr) {
            if (engine->canJIT(function))
                QV4::JIT::BaselineJIT(function).generate();
            else
                ++function->interpreterCallCount;
        }
    }
#endif // QT_CONFIG(qml_jit)

    // interpreter
    if (debugger)
        debugger->enteringFunction();

    ReturnedValue result;
    Q_ASSERT(function->kind != Function::AotCompiled);
    if (function->jittedCode != nullptr && debugger == nullptr) {
        result = function->jittedCode(frame, engine);
    } else {
        // interpreter
        result = interpret(frame, engine, function->codeData);
    }

    if (debugger)
        debugger->leavingFunction(result);

    return result;
}

QV4::ReturnedValue VME::interpret(JSTypesStackFrame *frame, ExecutionEngine *engine, const char *code)
{
    QV4::Function *function = frame->v4Function;
    QV4::Value &accumulator = frame->jsFrame->accumulator.asValue<Value>();
    QV4::ReturnedValue acc = accumulator.asReturnedValue();
    Value *stack = reinterpret_cast<Value *>(frame->jsFrame);

    MOTH_JUMP_TABLE;

    for (;;) {
    MOTH_DISPATCH()
    Q_UNREACHABLE(); // only reached when the dispatch doesn't jump somewhere

    MOTH_BEGIN_INSTR(LoadConst)
        acc = constant(function, index).asReturnedValue();
    MOTH_END_INSTR(LoadConst)

    MOTH_BEGIN_INSTR(LoadNull)
        acc = Encode::null();
    MOTH_END_INSTR(LoadNull)

    MOTH_BEGIN_INSTR(LoadZero)
        acc = Encode(static_cast<int>(0));
    MOTH_END_INSTR(LoadZero)

    MOTH_BEGIN_INSTR(LoadTrue)
        acc = Encode(true);
    MOTH_END_INSTR(LoadTrue)

    MOTH_BEGIN_INSTR(LoadFalse)
        acc = Encode(false);
    MOTH_END_INSTR(LoadFalse)

    MOTH_BEGIN_INSTR(LoadUndefined)
        acc = Encode::undefined();
    MOTH_END_INSTR(LoadUndefined)

    MOTH_BEGIN_INSTR(LoadInt)
        acc = Encode(value);
    MOTH_END_INSTR(LoadInt)

    MOTH_BEGIN_INSTR(MoveConst)
        STACK_VALUE(destTemp) = constant(function, constIndex);
    MOTH_END_INSTR(MoveConst)

    MOTH_BEGIN_INSTR(LoadReg)
        acc = STACK_VALUE(reg).asReturnedValue();
    MOTH_END_INSTR(LoadReg)

    MOTH_BEGIN_INSTR(StoreReg)
        STACK_VALUE(reg) = acc;
    MOTH_END_INSTR(StoreReg)

    MOTH_BEGIN_INSTR(MoveReg)
        STACK_VALUE(destReg) = STACK_VALUE(srcReg);
    MOTH_END_INSTR(MoveReg)

    MOTH_BEGIN_INSTR(LoadImport)
        acc = function->compilationUnit->imports[index]->asReturnedValue();
    MOTH_END_INSTR(LoadImport)

    MOTH_BEGIN_INSTR(LoadLocal)
        auto cc = static_cast<Heap::CallContext *>(STACK_VALUE(CallData::Context).m());
        Q_ASSERT(cc->type != QV4::Heap::CallContext::Type_GlobalContext);
        acc = cc->locals[index].asReturnedValue();
    MOTH_END_INSTR(LoadLocal)

    MOTH_BEGIN_INSTR(StoreLocal)
        CHECK_EXCEPTION;
        auto cc = static_cast<Heap::CallContext *>(STACK_VALUE(CallData::Context).m());
        Q_ASSERT(cc->type != QV4::Heap::CallContext::Type_GlobalContext);
        QV4::WriteBarrier::write(engine, cc, cc->locals.values[index].data_ptr(), acc);
    MOTH_END_INSTR(StoreLocal)

    MOTH_BEGIN_INSTR(LoadScopedLocal)
        auto cc = getScope(stack, scope);
        acc = cc->locals[index].asReturnedValue();
    MOTH_END_INSTR(LoadScopedLocal)

    MOTH_BEGIN_INSTR(StoreScopedLocal)
        CHECK_EXCEPTION;
        auto cc = getScope(stack, scope);
        QV4::WriteBarrier::write(engine, cc, cc->locals.values[index].data_ptr(), acc);
    MOTH_END_INSTR(StoreScopedLocal)

    MOTH_BEGIN_INSTR(LoadRuntimeString)
        acc = function->compilationUnit->runtimeStrings[stringId]->asReturnedValue();
    MOTH_END_INSTR(LoadRuntimeString)

    MOTH_BEGIN_INSTR(MoveRegExp)
        STACK_VALUE(destReg) = Runtime::RegexpLiteral::call(engine, regExpId);
    MOTH_END_INSTR(MoveRegExp)

    MOTH_BEGIN_INSTR(LoadClosure)
        acc = Runtime::Closure::call(engine, value);
    MOTH_END_INSTR(LoadClosure)

    MOTH_BEGIN_INSTR(LoadName)
        STORE_IP();
        acc = Runtime::LoadName::call(engine, name);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(LoadName)

    MOTH_BEGIN_INSTR(LoadGlobalLookup)
        STORE_IP();
        QV4::Lookup *l = function->executableCompilationUnit()->runtimeLookups + index;
        acc = l->globalGetter(engine);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(LoadGlobalLookup)

    MOTH_BEGIN_INSTR(LoadQmlContextPropertyLookup)
        STORE_IP();
        QV4::Lookup *l = function->executableCompilationUnit()->runtimeLookups + index;
        acc = l->contextGetter(engine, nullptr);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(LoadQmlContextPropertyLookup)

    MOTH_BEGIN_INSTR(StoreNameStrict)
        STORE_IP();
        STORE_ACC();
        Runtime::StoreNameStrict::call(engine, name, accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(StoreNameStrict)

    MOTH_BEGIN_INSTR(StoreNameSloppy)
        STORE_IP();
        STORE_ACC();
        Runtime::StoreNameSloppy::call(engine, name, accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(StoreNameSloppy)

    MOTH_BEGIN_INSTR(LoadElement)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::LoadElement::call(engine, STACK_VALUE(base), accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(LoadElement)

    MOTH_BEGIN_INSTR(StoreElement)
        STORE_IP();
        STORE_ACC();
        Runtime::StoreElement::call(engine, STACK_VALUE(base), STACK_VALUE(index), accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(StoreElement)

    MOTH_BEGIN_INSTR(LoadProperty)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::LoadProperty::call(engine, accumulator, name);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(LoadProperty)

    MOTH_BEGIN_INSTR(LoadOptionalProperty)
        STORE_IP();
        STORE_ACC();
        if (accumulator.isNullOrUndefined()) {
            acc = Encode::undefined();
            code += offset;
        } else {
            acc = Runtime::LoadProperty::call(engine, accumulator, name);
        }
        CHECK_EXCEPTION;
    MOTH_END_INSTR(LoadOptionalProperty)

    MOTH_BEGIN_INSTR(GetLookup)
        STORE_IP();
        STORE_ACC();

        QV4::Lookup *l = function->executableCompilationUnit()->runtimeLookups + index;

        if (accumulator.isNullOrUndefined()) {
            QString message = QStringLiteral("Cannot read property '%1' of %2")
                    .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString())
                    .arg(accumulator.toQStringNoThrow());
            acc = engine->throwTypeError(message);
            goto handleUnwind;
        }

        acc = l->getter(engine, accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(GetLookup)

    MOTH_BEGIN_INSTR(GetOptionalLookup)
        STORE_IP();
        STORE_ACC();

        QV4::Lookup *l = function->executableCompilationUnit()->runtimeLookups + index;

        if (accumulator.isNullOrUndefined()) {
            code += offset;
        } else {
            acc = l->getter(engine, accumulator);
        }
    CHECK_EXCEPTION;
    MOTH_END_INSTR(GetOptionalLookup)

    MOTH_BEGIN_INSTR(StoreProperty)
        STORE_IP();
        STORE_ACC();
        Runtime::StoreProperty::call(engine, STACK_VALUE(base), name, accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(StoreProperty)

    MOTH_BEGIN_INSTR(SetLookup)
        STORE_IP();
        STORE_ACC();
        QV4::Lookup *l = function->executableCompilationUnit()->runtimeLookups + index;
        if (!l->setter(engine, STACK_VALUE(base), accumulator) && function->isStrict())
            engine->throwTypeError();
        CHECK_EXCEPTION;
    MOTH_END_INSTR(SetLookup)

    MOTH_BEGIN_INSTR(LoadSuperProperty)
        STORE_IP();
        acc = Runtime::LoadSuperProperty::call(engine, STACK_VALUE(property));
        CHECK_EXCEPTION;
    MOTH_END_INSTR(LoadSuperProperty)

    MOTH_BEGIN_INSTR(StoreSuperProperty)
        STORE_IP();
        STORE_ACC();
        Runtime::StoreSuperProperty::call(engine, STACK_VALUE(property), accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(StoreSuperProperty)

    MOTH_BEGIN_INSTR(Yield)
        frame->setYield(code);
        frame->setYieldIsIterator(false);
        return acc;
    MOTH_END_INSTR(Yield)

    MOTH_BEGIN_INSTR(YieldStar)
        frame->setYield(code);
        frame->setYieldIsIterator(true);
        return acc;
    MOTH_END_INSTR(YieldStar)

    MOTH_BEGIN_INSTR(Resume)
        // check exception, in case the generator was called with throw() or return()
        if (engine->hasException) {
            // an empty value indicates that the generator was called with return()
            if (engine->exceptionValue->asReturnedValue() != Value::emptyValue().asReturnedValue())
                goto handleUnwind;
            engine->hasException = false;
            *engine->exceptionValue = Value::undefinedValue();
        } else {
            code += offset;
        }
    MOTH_END_INSTR(Resume)

    MOTH_BEGIN_INSTR(IteratorNextForYieldStar)
        STORE_ACC();
        acc = Runtime::IteratorNextForYieldStar::call(engine, accumulator, STACK_VALUE(iterator), &STACK_VALUE(object));
        if (ACC.toBoolean())
            code += offset;
    MOTH_END_INSTR(IteratorNextForYieldStar)

    MOTH_BEGIN_INSTR(CallValue)
        STORE_IP();
        Value func = STACK_VALUE(name);
        if (Q_UNLIKELY(!func.isFunctionObject())) {
            acc = engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow()));
            goto handleUnwind;
        }
        Value undef = Value::undefinedValue();
        acc = static_cast<const FunctionObject &>(func).call(&undef, stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CallValue)

    MOTH_BEGIN_INSTR(CallWithReceiver)
        STORE_IP();
        Value func = STACK_VALUE(name);
        if (Q_UNLIKELY(!func.isFunctionObject())) {
            acc = engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow()));
            goto handleUnwind;
        }
        acc = static_cast<const FunctionObject &>(func).call(stack + thisObject, stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CallWithReceiver)

    MOTH_BEGIN_INSTR(CallProperty)
        STORE_IP();
        acc = Runtime::CallProperty::call(engine, STACK_VALUE(base), name, stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CallProperty)

    MOTH_BEGIN_INSTR(CallPropertyLookup)
        STORE_IP();
        Lookup *l = function->executableCompilationUnit()->runtimeLookups + lookupIndex;

        if (STACK_VALUE(base).isNullOrUndefined()) {
            QString message = QStringLiteral("Cannot call method '%1' of %2")
                    .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString())
                    .arg(STACK_VALUE(base).toQStringNoThrow());
            acc = engine->throwTypeError(message);
            goto handleUnwind;
        }

        // ok to have the value on the stack here
        Value f = Value::fromReturnedValue(l->getter(engine, STACK_VALUE(base)));

        if (Q_LIKELY(f.isFunctionObject())) {
            acc = static_cast<FunctionObject &>(f).call(stack + base, stack + argv, argc);
        } else if (QmlSignalHandler *handler = f.as<QmlSignalHandler>()) {
            acc = handler->call(stack + base, stack + argv, argc);
        } else {
            const QString message = QStringLiteral("Property '%1' of object %2 is not a function")
                    .arg(engine->currentStackFrame->v4Function->compilationUnit
                                                   ->runtimeStrings[l->nameIndex]->toQString())
                    .arg(STACK_VALUE(base).toQStringNoThrow());
            acc = engine->throwTypeError(message);
            goto handleUnwind;
        }

        CHECK_EXCEPTION;
    MOTH_END_INSTR(CallPropertyLookup)

    MOTH_BEGIN_INSTR(CallName)
        STORE_IP();
        acc = Runtime::CallName::call(engine, name, stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CallName)

    MOTH_BEGIN_INSTR(CallPossiblyDirectEval)
        STORE_IP();
        acc = Runtime::CallPossiblyDirectEval::call(engine, stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CallPossiblyDirectEval)

    MOTH_BEGIN_INSTR(CallGlobalLookup)
        STORE_IP();
        acc = Runtime::CallGlobalLookup::call(engine, index, stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CallGlobalLookup)

    MOTH_BEGIN_INSTR(CallQmlContextPropertyLookup)
        STORE_IP();
        acc = Runtime::CallQmlContextPropertyLookup::call(engine, index, stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CallQmlContextPropertyLookup)

    MOTH_BEGIN_INSTR(CallWithSpread)
        STORE_IP();
        acc = Runtime::CallWithSpread::call(engine, STACK_VALUE(func), STACK_VALUE(thisObject), stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CallWithSpread)

    MOTH_BEGIN_INSTR(TailCall)
        STORE_IP();
        *engine->jsAlloca(1) = Primitive::fromInt32(argc);
        *engine->jsAlloca(1) = Primitive::fromInt32(argv);
        *engine->jsAlloca(1) = STACK_VALUE(thisObject);
        *engine->jsAlloca(1) = STACK_VALUE(func);
        return Runtime::TailCall::call(frame, engine);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(TailCall)

    MOTH_BEGIN_INSTR(Construct)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::Construct::call(engine, STACK_VALUE(func), ACC, stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(Construct)

    MOTH_BEGIN_INSTR(ConstructWithSpread)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::ConstructWithSpread::call(engine, STACK_VALUE(func), ACC, stack + argv, argc);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(ConstructWithSpread)

    MOTH_BEGIN_INSTR(SetUnwindHandler)
        frame->unwindHandler = offset ? code + offset : nullptr;
    MOTH_END_INSTR(SetUnwindHandler)

    MOTH_BEGIN_INSTR(UnwindDispatch)
        CHECK_EXCEPTION;
        if (frame->unwindLevel) {
            --frame->unwindLevel;
            if (frame->unwindLevel)
                goto handleUnwind;
            code = frame->unwindLabel;
        }
    MOTH_END_INSTR(UnwindDispatch)

    MOTH_BEGIN_INSTR(UnwindToLabel)
        frame->unwindLevel = level;
        frame->unwindLabel = code + offset;
        goto handleUnwind;
    MOTH_END_INSTR(UnwindToLabel)

    MOTH_BEGIN_INSTR(DeadTemporalZoneCheck)
        if (ACC.isEmpty()) {
            STORE_IP();
            Runtime::ThrowReferenceError::call(engine, name);
            goto handleUnwind;
        }
    MOTH_END_INSTR(DeadTemporalZoneCheck)

    MOTH_BEGIN_INSTR(ThrowException)
        STORE_IP();
        STORE_ACC();
        Runtime::ThrowException::call(engine, accumulator);
        goto handleUnwind;
    MOTH_END_INSTR(ThrowException)

    MOTH_BEGIN_INSTR(GetException)
        acc = engine->hasException ? engine->exceptionValue->asReturnedValue()
                                   : Value::emptyValue().asReturnedValue();
        engine->hasException = false;
    MOTH_END_INSTR(GetException)

    MOTH_BEGIN_INSTR(SetException)
        if (acc != Value::emptyValue().asReturnedValue()) {
            *engine->exceptionValue = acc;
            engine->hasException = true;
        }
    MOTH_END_INSTR(SetException)

    MOTH_BEGIN_INSTR(PushCatchContext)
        Runtime::PushCatchContext::call(engine, index, name);
    MOTH_END_INSTR(PushCatchContext)

    MOTH_BEGIN_INSTR(CreateCallContext)
        Runtime::PushCallContext::call(frame);
    MOTH_END_INSTR(CreateCallContext)

    MOTH_BEGIN_INSTR(PushWithContext)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::PushWithContext::call(engine, STACK_VALUE(CallData::Accumulator));
        CHECK_EXCEPTION;
    MOTH_END_INSTR(PushWithContext)

    MOTH_BEGIN_INSTR(PushBlockContext)
        STORE_ACC();
        Runtime::PushBlockContext::call(engine, index);
    MOTH_END_INSTR(PushBlockContext)

    MOTH_BEGIN_INSTR(CloneBlockContext)
        STORE_ACC();
        Runtime::CloneBlockContext::call(engine);
    MOTH_END_INSTR(CloneBlockContext)

    MOTH_BEGIN_INSTR(PushScriptContext)
        Runtime::PushScriptContext::call(engine, index);
    MOTH_END_INSTR(PushScriptContext)

    MOTH_BEGIN_INSTR(PopScriptContext)
        Runtime::PopScriptContext::call(engine);
    MOTH_END_INSTR(PopScriptContext)

    MOTH_BEGIN_INSTR(PopContext)
        ExecutionContext *c = static_cast<ExecutionContext *>(stack + CallData::Context);
        STACK_VALUE(CallData::Context) = c->d()->outer;
    MOTH_END_INSTR(PopContext)

    MOTH_BEGIN_INSTR(GetIterator)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::GetIterator::call(engine, accumulator, iterator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(GetIterator)

    MOTH_BEGIN_INSTR(IteratorNext)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::IteratorNext::call(engine, accumulator, &STACK_VALUE(value));
        if (ACC.toBoolean())
            code += offset;
    MOTH_END_INSTR(IteratorNext)

    MOTH_BEGIN_INSTR(IteratorClose)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::IteratorClose::call(engine, accumulator);
    MOTH_END_INSTR(IteratorClose)

    MOTH_BEGIN_INSTR(DestructureRestElement)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::DestructureRestElement::call(engine, ACC);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(DestructureRestElement)

    MOTH_BEGIN_INSTR(DeleteProperty)
        acc = Runtime::DeleteProperty::call(engine, function, STACK_VALUE(base), STACK_VALUE(index));
        CHECK_EXCEPTION;
    MOTH_END_INSTR(DeleteProperty)

    MOTH_BEGIN_INSTR(DeleteName)
        acc = Runtime::DeleteName::call(engine, function, name);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(DeleteName)

    MOTH_BEGIN_INSTR(TypeofName)
        acc = Runtime::TypeofName::call(engine, name);
    MOTH_END_INSTR(TypeofName)

    MOTH_BEGIN_INSTR(TypeofValue)
        STORE_ACC();
        acc = Runtime::TypeofValue::call(engine, accumulator);
    MOTH_END_INSTR(TypeofValue)

    MOTH_BEGIN_INSTR(DeclareVar)
        Runtime::DeclareVar::call(engine, isDeletable, varName);
    MOTH_END_INSTR(DeclareVar)

    MOTH_BEGIN_INSTR(DefineArray)
        QV4::Value *arguments = stack + args;
        acc = Runtime::ArrayLiteral::call(engine, arguments, argc);
    MOTH_END_INSTR(DefineArray)

    MOTH_BEGIN_INSTR(DefineObjectLiteral)
        QV4::Value *arguments = stack + args;
        acc = Runtime::ObjectLiteral::call(engine, internalClassId, arguments, argc);
    MOTH_END_INSTR(DefineObjectLiteral)

    MOTH_BEGIN_INSTR(CreateClass)
        acc = Runtime::CreateClass::call(engine, classIndex, STACK_VALUE(heritage), stack + computedNames);
    MOTH_END_INSTR(CreateClass)

    MOTH_BEGIN_INSTR(CreateMappedArgumentsObject)
        acc = Runtime::CreateMappedArgumentsObject::call(engine);
    MOTH_END_INSTR(CreateMappedArgumentsObject)

    MOTH_BEGIN_INSTR(CreateUnmappedArgumentsObject)
        acc = Runtime::CreateUnmappedArgumentsObject::call(engine);
    MOTH_END_INSTR(CreateUnmappedArgumentsObject)

    MOTH_BEGIN_INSTR(CreateRestParameter)
        acc = Runtime::CreateRestParameter::call(engine, argIndex);
    MOTH_END_INSTR(CreateRestParameter)

    MOTH_BEGIN_INSTR(ConvertThisToObject)
        STORE_ACC();
        stack[CallData::This] = Runtime::ConvertThisToObject::call(
                    engine, STACK_VALUE(CallData::This));
        CHECK_EXCEPTION;
    MOTH_END_INSTR(ConvertThisToObject)

    MOTH_BEGIN_INSTR(LoadSuperConstructor)
        acc = Runtime::LoadSuperConstructor::call(engine, STACK_VALUE(CallData::Function));
        CHECK_EXCEPTION;
    MOTH_END_INSTR(LoadSuperConstructor)

    MOTH_BEGIN_INSTR(ToObject)
        STORE_ACC();
        acc = ACC.toObject(engine)->asReturnedValue();
        CHECK_EXCEPTION;
    MOTH_END_INSTR(ToObject)

    MOTH_BEGIN_INSTR(Jump)
        code += offset;
    MOTH_END_INSTR(Jump)

    MOTH_BEGIN_INSTR(JumpTrue)
        bool takeJump;
        if (Q_LIKELY(ACC.integerCompatible()))
            takeJump = ACC.int_32();
        else
            takeJump = ACC.toBoolean();
        if (takeJump)
            code += offset;
    MOTH_END_INSTR(JumpTrue)

    MOTH_BEGIN_INSTR(JumpFalse)
        bool takeJump;
        if (Q_LIKELY(ACC.integerCompatible()))
            takeJump = !ACC.int_32();
        else
            takeJump = !ACC.toBoolean();
        if (takeJump)
            code += offset;
    MOTH_END_INSTR(JumpFalse)

    MOTH_BEGIN_INSTR(JumpNoException)
        if (!engine->hasException)
            code += offset;
    MOTH_END_INSTR(JumpNoException)

    MOTH_BEGIN_INSTR(JumpNotUndefined)
        if (Q_LIKELY(acc != QV4::Encode::undefined()))
            code += offset;
    MOTH_END_INSTR(JumpNotUndefined)

    MOTH_BEGIN_INSTR(CheckException)
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CheckException)

    MOTH_BEGIN_INSTR(CmpEqNull)
        acc = Encode(ACC.isNullOrUndefined());
    MOTH_END_INSTR(CmpEqNull)

    MOTH_BEGIN_INSTR(CmpNeNull)
        acc = Encode(!ACC.isNullOrUndefined());
    MOTH_END_INSTR(CmpNeNull)

    MOTH_BEGIN_INSTR(CmpEqInt)
        if (ACC.isIntOrBool()) {
            acc = Encode(ACC.int_32() == lhs);
        } else {
            STORE_ACC();
            acc = Encode(compareEqualInt(accumulator, ACC, lhs));
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(CmpEqInt)

    MOTH_BEGIN_INSTR(CmpNeInt)
        if (ACC.isIntOrBool()) {
            acc = Encode(bool(ACC.int_32() != lhs));
        } else {
            STORE_ACC();
            acc = Encode(!compareEqualInt(accumulator, ACC, lhs));
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(CmpNeInt)

    MOTH_BEGIN_INSTR(CmpEq)
        const Value left = STACK_VALUE(lhs);
        if (Q_LIKELY(left.asReturnedValue() == ACC.asReturnedValue())) {
            acc = Encode(!ACC.isNaN());
        } else if (Q_LIKELY(left.isInteger() && ACC.isInteger())) {
            acc = Encode(left.int_32() == ACC.int_32());
        } else {
            STORE_ACC();
            acc = Encode(bool(Runtime::CompareEqual::call(left, accumulator)));
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(CmpEq)

    MOTH_BEGIN_INSTR(CmpNe)
        const Value left = STACK_VALUE(lhs);
        if (Q_LIKELY(left.isInteger() && ACC.isInteger())) {
            acc = Encode(bool(left.int_32() != ACC.int_32()));
        } else {
            STORE_ACC();
            acc = Encode(bool(!Runtime::CompareEqual::call(left, accumulator)));
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(CmpNe)

    MOTH_BEGIN_INSTR(CmpGt)
        const Value left = STACK_VALUE(lhs);
        if (Q_LIKELY(left.isInteger() && ACC.isInteger())) {
            acc = Encode(left.int_32() > ACC.int_32());
        } else if (left.isNumber() && ACC.isNumber()) {
            acc = Encode(left.asDouble() > ACC.asDouble());
        } else {
            STORE_ACC();
            acc = Encode(bool(Runtime::CompareGreaterThan::call(left, accumulator)));
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(CmpGt)

    MOTH_BEGIN_INSTR(CmpGe)
        const Value left = STACK_VALUE(lhs);
        if (Q_LIKELY(left.isInteger() && ACC.isInteger())) {
            acc = Encode(left.int_32() >= ACC.int_32());
        } else if (left.isNumber() && ACC.isNumber()) {
            acc = Encode(left.asDouble() >= ACC.asDouble());
        } else {
            STORE_ACC();
            acc = Encode(bool(Runtime::CompareGreaterEqual::call(left, accumulator)));
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(CmpGe)

    MOTH_BEGIN_INSTR(CmpLt)
        const Value left = STACK_VALUE(lhs);
        if (Q_LIKELY(left.isInteger() && ACC.isInteger())) {
            acc = Encode(left.int_32() < ACC.int_32());
        } else if (left.isNumber() && ACC.isNumber()) {
            acc = Encode(left.asDouble() < ACC.asDouble());
        } else {
            STORE_ACC();
            acc = Encode(bool(Runtime::CompareLessThan::call(left, accumulator)));
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(CmpLt)

    MOTH_BEGIN_INSTR(CmpLe)
        const Value left = STACK_VALUE(lhs);
        if (Q_LIKELY(left.isInteger() && ACC.isInteger())) {
            acc = Encode(left.int_32() <= ACC.int_32());
        } else if (left.isNumber() && ACC.isNumber()) {
            acc = Encode(left.asDouble() <= ACC.asDouble());
        } else {
            STORE_ACC();
            acc = Encode(bool(Runtime::CompareLessEqual::call(left, accumulator)));
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(CmpLe)

    MOTH_BEGIN_INSTR(CmpStrictEqual)
        if (STACK_VALUE(lhs).rawValue() == ACC.rawValue() && !ACC.isNaN()) {
            acc = Encode(true);
        } else {
            STORE_ACC();
            acc = Runtime::StrictEqual::call(STACK_VALUE(lhs), accumulator);
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(CmpStrictEqual)

    MOTH_BEGIN_INSTR(CmpStrictNotEqual)
        if (STACK_VALUE(lhs).rawValue() != ACC.rawValue() || ACC.isNaN()) {
            STORE_ACC();
            acc = Runtime::StrictNotEqual::call(STACK_VALUE(lhs), accumulator);
            CHECK_EXCEPTION;
        } else {
            acc = Encode(false);
        }
    MOTH_END_INSTR(CmpStrictNotEqual)

    MOTH_BEGIN_INSTR(CmpIn)
        STORE_IP();
        STORE_ACC();
        acc = Runtime::In::call(engine, STACK_VALUE(lhs), accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CmpIn)

    MOTH_BEGIN_INSTR(CmpInstanceOf)
        STORE_ACC();
        acc = Runtime::Instanceof::call(engine, STACK_VALUE(lhs), ACC);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(CmpInstanceOf)

    MOTH_BEGIN_INSTR(UNot)
        if (ACC.integerCompatible()) {
            acc = Encode(!static_cast<bool>(ACC.int_32()));
        } else {
            acc = Encode(!Value::toBooleanImpl(ACC));
        }
    MOTH_END_INSTR(UNot)

    MOTH_BEGIN_INSTR(UPlus)
        if (Q_UNLIKELY(!ACC.isNumber())) {
            acc = Encode(ACC.toNumberImpl());
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(UPlus)

    MOTH_BEGIN_INSTR(UMinus)
        if (Q_LIKELY(ACC.integerCompatible())) {
            int a = ACC.int_32();
            if (a == 0 || a == std::numeric_limits<int>::min()) {
                acc = Encode(-static_cast<double>(a));
            } else {
                acc = sub_int32(0, ACC.int_32());
            }
        } else if (ACC.isDouble()) {
            acc ^= (1ull << 63); // simply flip sign bit
        } else {
            acc = Encode(-ACC.toNumberImpl());
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(UMinus)

    MOTH_BEGIN_INSTR(UCompl)
        VALUE_TO_INT(a, ACC);
        acc = Encode(~a);
    MOTH_END_INSTR(UCompl)

    MOTH_BEGIN_INSTR(Increment)
        if (Q_LIKELY(ACC.integerCompatible())) {
            acc = add_int32(ACC.int_32(), 1);
        } else if (ACC.isDouble()) {
            acc = QV4::Encode(ACC.doubleValue() + 1.);
        } else {
            acc = Encode(ACC.toNumberImpl() + 1.);
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(Increment)

    MOTH_BEGIN_INSTR(Decrement)
        if (Q_LIKELY(ACC.integerCompatible())) {
            acc = sub_int32(ACC.int_32(), 1);
        } else if (ACC.isDouble()) {
            acc = QV4::Encode(ACC.doubleValue() - 1.);
        } else {
            acc = Encode(ACC.toNumberImpl() - 1.);
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(Decrement)

    MOTH_BEGIN_INSTR(Add)
        const Value left = STACK_VALUE(lhs);
        if (Q_LIKELY(Value::integerCompatible(left, ACC))) {
            acc = add_int32(left.int_32(), ACC.int_32());
        } else if (left.isNumber() && ACC.isNumber()) {
            acc = Encode(left.asDouble() + ACC.asDouble());
        } else {
            STORE_ACC();
            acc = Runtime::Add::call(engine, left, accumulator);
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(Add)

    MOTH_BEGIN_INSTR(Sub)
        const Value left = STACK_VALUE(lhs);
        if (Q_LIKELY(Value::integerCompatible(left, ACC))) {
            acc = sub_int32(left.int_32(), ACC.int_32());
        } else if (left.isNumber() && ACC.isNumber()) {
            acc = Encode(left.asDouble() - ACC.asDouble());
        } else {
            STORE_ACC();
            acc = Runtime::Sub::call(left, accumulator);
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(Sub)

    MOTH_BEGIN_INSTR(As)
        const Value left = STACK_VALUE(lhs);
        STORE_ACC();
        acc = Runtime::As::call(engine, left, accumulator);
    MOTH_END_INSTR(As)

    MOTH_BEGIN_INSTR(Exp)
        const Value left = STACK_VALUE(lhs);
        double base = left.toNumber();
        double exp = ACC.toNumber();
        acc = Encode(QQmlPrivate::jsExponentiate(base, exp));
    MOTH_END_INSTR(Exp)

    MOTH_BEGIN_INSTR(Mul)
        const Value left = STACK_VALUE(lhs);
        if (Q_LIKELY(Value::integerCompatible(left, ACC))) {
            acc = mul_int32(left.int_32(), ACC.int_32());
        } else if (left.isNumber() && ACC.isNumber()) {
            acc = Encode(left.asDouble() * ACC.asDouble());
        } else {
            STORE_ACC();
            acc = Runtime::Mul::call(left, accumulator);
            CHECK_EXCEPTION;
        }
    MOTH_END_INSTR(Mul)

    MOTH_BEGIN_INSTR(Div)
        STORE_ACC();
        acc = Runtime::Div::call(STACK_VALUE(lhs), accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(Div)

    MOTH_BEGIN_INSTR(Mod)
        STORE_ACC();
        acc = Runtime::Mod::call(STACK_VALUE(lhs), accumulator);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(Mod)

    MOTH_BEGIN_INSTR(BitAnd)
        VALUE_TO_INT(l, STACK_VALUE(lhs));
        VALUE_TO_INT(a, ACC);
        acc = Encode(l & a);
    MOTH_END_INSTR(BitAnd)

    MOTH_BEGIN_INSTR(BitOr)
        VALUE_TO_INT(l, STACK_VALUE(lhs));
        VALUE_TO_INT(a, ACC);
        acc = Encode(l | a);
    MOTH_END_INSTR(BitOr)

    MOTH_BEGIN_INSTR(BitXor)
        VALUE_TO_INT(l, STACK_VALUE(lhs));
        VALUE_TO_INT(a, ACC);
        acc = Encode(l ^ a);
    MOTH_END_INSTR(BitXor)

    MOTH_BEGIN_INSTR(UShr)
        VALUE_TO_INT(l, STACK_VALUE(lhs));
        VALUE_TO_INT(a, ACC);
        acc = Encode(static_cast<uint>(l) >> uint(a & 0x1f));
    MOTH_END_INSTR(UShr)

    MOTH_BEGIN_INSTR(Shr)
        VALUE_TO_INT(l, STACK_VALUE(lhs));
        VALUE_TO_INT(a, ACC);
        acc = Encode(l >> (a & 0x1f));
    MOTH_END_INSTR(Shr)

    MOTH_BEGIN_INSTR(Shl)
        VALUE_TO_INT(l, STACK_VALUE(lhs));
        VALUE_TO_INT(a, ACC);
        acc = Encode(l << (a & 0x1f));
    MOTH_END_INSTR(Shl)

    MOTH_BEGIN_INSTR(BitAndConst)
        VALUE_TO_INT(a, ACC);
        acc = Encode(a & rhs);
        CHECK_EXCEPTION;
    MOTH_END_INSTR(BitAndConst)

    MOTH_BEGIN_INSTR(BitOrConst)
        VALUE_TO_INT(a, ACC);
        acc = Encode(a | rhs);
    MOTH_END_INSTR(BitOrConst)

    MOTH_BEGIN_INSTR(BitXorConst)
        VALUE_TO_INT(a, ACC);
        acc = Encode(a ^ rhs);
    MOTH_END_INSTR(BitXorConst)

    MOTH_BEGIN_INSTR(UShrConst)
        acc = Encode(ACC.toUInt32() >> uint(rhs));
    MOTH_END_INSTR(UShrConst)

    MOTH_BEGIN_INSTR(ShrConst)
        VALUE_TO_INT(a, ACC);
        acc = Encode(a >> rhs);
    MOTH_END_INSTR(ShrConst)

    MOTH_BEGIN_INSTR(ShlConst)
        VALUE_TO_INT(a, ACC);
        acc = Encode(a << rhs);
    MOTH_END_INSTR(ShlConst)

    MOTH_BEGIN_INSTR(Ret)
        return acc;
    MOTH_END_INSTR(Ret)

    MOTH_BEGIN_INSTR(InitializeBlockDeadTemporalZone)
        acc = Encode(Value::emptyValue());
        for (int i = firstReg, end = firstReg + count; i < end; ++i)
            STACK_VALUE(i) = acc;
    MOTH_END_INSTR(InitializeBlockDeadTemporalZone)

    MOTH_BEGIN_INSTR(ThrowOnNullOrUndefined)
        if (Value::fromReturnedValue(acc).isNullOrUndefined()) {
            engine->throwTypeError();
            goto handleUnwind;
        }
    MOTH_END_INSTR(ThrowOnNullOrUndefined)

    MOTH_BEGIN_INSTR(GetTemplateObject)
        acc = Runtime::GetTemplateObject::call(function, index);
    MOTH_END_INSTR(GetTemplateObject)

    MOTH_BEGIN_INSTR(Debug)
#if QT_CONFIG(qml_debug)
        STORE_IP();
        debug_slowPath(engine);
#endif // QT_CONFIG(qml_debug)
    MOTH_END_INSTR(Debug)

    handleUnwind:
        // We do start the exception handler in case of isInterrupted. The exception handler will
        // immediately abort, due to the same isInterrupted. We don't skip the exception handler
        // because the current behavior is easier to implement in the JIT.
        Q_ASSERT(engine->hasException || engine->isInterrupted.loadRelaxed() || frame->unwindLevel);
        if (!frame->unwindHandler) {
            acc = Encode::undefined();
            return acc;
        }
        code = frame->unwindHandler;
    }
}
