/****************************************************************************
**
** Copyright (C) 2016 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 "qv4isel_util_p.h"
#include "qv4isel_moth_p.h"
#include "qv4ssa_p.h"
#include <private/qv4compileddata_p.h>
#include <wtf/MathExtras.h>

#if !defined(V4_BOOTSTRAP)
#include "qv4vme_moth_p.h"
#include <private/qv4function_p.h>
#endif

#undef USE_TYPE_INFO

using namespace QV4;
using namespace QV4::Moth;

namespace {

inline QV4::Runtime::RuntimeMethods aluOpFunction(IR::AluOp op)
{
    switch (op) {
    case IR::OpInvalid:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpIfTrue:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpNot:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpUMinus:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpUPlus:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpCompl:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpBitAnd:
        return QV4::Runtime::bitAnd;
    case IR::OpBitOr:
        return QV4::Runtime::bitOr;
    case IR::OpBitXor:
        return QV4::Runtime::bitXor;
    case IR::OpAdd:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpSub:
        return QV4::Runtime::sub;
    case IR::OpMul:
        return QV4::Runtime::mul;
    case IR::OpDiv:
        return QV4::Runtime::div;
    case IR::OpMod:
        return QV4::Runtime::mod;
    case IR::OpLShift:
        return QV4::Runtime::shl;
    case IR::OpRShift:
        return QV4::Runtime::shr;
    case IR::OpURShift:
        return QV4::Runtime::ushr;
    case IR::OpGt:
        return QV4::Runtime::greaterThan;
    case IR::OpLt:
        return QV4::Runtime::lessThan;
    case IR::OpGe:
        return QV4::Runtime::greaterEqual;
    case IR::OpLe:
        return QV4::Runtime::lessEqual;
    case IR::OpEqual:
        return QV4::Runtime::equal;
    case IR::OpNotEqual:
        return QV4::Runtime::notEqual;
    case IR::OpStrictEqual:
        return QV4::Runtime::strictEqual;
    case IR::OpStrictNotEqual:
        return QV4::Runtime::strictNotEqual;
    case IR::OpInstanceof:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpIn:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpAnd:
        return QV4::Runtime::InvalidRuntimeMethod;
    case IR::OpOr:
        return QV4::Runtime::InvalidRuntimeMethod;
    default:
        Q_ASSERT(!"Unknown AluOp");
        return QV4::Runtime::InvalidRuntimeMethod;
    }
};

inline bool isNumberType(IR::Expr *e)
{
    switch (e->type) {
    case IR::SInt32Type:
    case IR::UInt32Type:
    case IR::DoubleType:
        return true;
    default:
        return false;
    }
}

inline bool isIntegerType(IR::Expr *e)
{
    switch (e->type) {
    case IR::SInt32Type:
    case IR::UInt32Type:
        return true;
    default:
        return false;
    }
}

inline bool isBoolType(IR::Expr *e)
{
    return (e->type == IR::BoolType);
}

} // anonymous namespace

InstructionSelection::InstructionSelection(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator, EvalISelFactory *iselFactory)
    : EvalInstructionSelection(execAllocator, module, jsGenerator, iselFactory)
    , qmlEngine(qmlEngine)
    , _block(0)
    , _codeStart(0)
    , _codeNext(0)
    , _codeEnd(0)
    , _currentStatement(0)
    , compilationUnit(new CompilationUnit)
{
    setUseTypeInference(false);
}

InstructionSelection::~InstructionSelection()
{
}

void InstructionSelection::run(int functionIndex)
{
    IR::Function *function = irModule->functions[functionIndex];
    IR::BasicBlock *block = 0, *nextBlock = 0;

    QHash<IR::BasicBlock *, QVector<ptrdiff_t> > patches;
    QHash<IR::BasicBlock *, ptrdiff_t> addrs;

    int codeSize = 4096;
    uchar *codeStart = new uchar[codeSize];
    memset(codeStart, 0, codeSize);
    uchar *codeNext = codeStart;
    uchar *codeEnd = codeStart + codeSize;

    qSwap(_function, function);
    qSwap(block, _block);
    qSwap(nextBlock, _nextBlock);
    qSwap(patches, _patches);
    qSwap(addrs, _addrs);
    qSwap(codeStart, _codeStart);
    qSwap(codeNext, _codeNext);
    qSwap(codeEnd, _codeEnd);

    IR::Optimizer opt(_function);
    opt.run(qmlEngine, useTypeInference, /*peelLoops =*/ false);
    if (opt.isInSSA()) {
        static const bool doStackSlotAllocation =
                qEnvironmentVariableIsEmpty("QV4_NO_INTERPRETER_STACK_SLOT_ALLOCATION");

        if (doStackSlotAllocation) {
            IR::AllocateStackSlots(opt.lifeTimeIntervals()).forFunction(_function);
        } else {
            opt.convertOutOfSSA();
            ConvertTemps().toStackSlots(_function);
        }
        opt.showMeTheCode(_function, "After stack slot allocation");
    } else {
        ConvertTemps().toStackSlots(_function);
    }

    BitVector removableJumps = opt.calculateOptionalJumps();
    qSwap(_removableJumps, removableJumps);

    IR::Stmt *cs = 0;
    qSwap(_currentStatement, cs);

    int locals = frameSize();
    Q_ASSERT(locals >= 0);

    IR::BasicBlock *exceptionHandler = 0;

    Instruction::Push push;
    push.value = quint32(locals);
    addInstruction(push);

    currentLine = 0;
    const QVector<IR::BasicBlock *> &basicBlocks = _function->basicBlocks();
    for (int i = 0, ei = basicBlocks.size(); i != ei; ++i) {
        blockNeedsDebugInstruction = irModule->debugMode;
        _block = basicBlocks[i];
        _nextBlock = (i < ei - 1) ? basicBlocks[i + 1] : 0;
        _addrs.insert(_block, _codeNext - _codeStart);

        if (_block->catchBlock != exceptionHandler) {
            Instruction::SetExceptionHandler set;
            set.offset = 0;
            if (_block->catchBlock) {
                ptrdiff_t loc = addInstruction(set) + (((const char *)&set.offset) - ((const char *)&set));
                _patches[_block->catchBlock].append(loc);
            } else {
                addInstruction(set);
            }
            exceptionHandler = _block->catchBlock;
        } else if (_block->catchBlock == nullptr && _block->index() != 0 && _block->in.isEmpty()) {
            exceptionHandler = nullptr;
            Instruction::SetExceptionHandler set;
            set.offset = 0;
            addInstruction(set);
        }

        for (IR::Stmt *s : _block->statements()) {
            _currentStatement = s;

            if (s->location.isValid()) {
                if (s->location.startLine != currentLine) {
                    blockNeedsDebugInstruction = false;
                    currentLine = s->location.startLine;
#ifndef QT_NO_QML_DEBUGGER
                    if (irModule->debugMode) {
                        Instruction::Debug debug;
                        debug.lineNumber = currentLine;
                        addInstruction(debug);
                    } else {
                        Instruction::Line line;
                        line.lineNumber = currentLine;
                        addInstruction(line);
                    }
#endif
                }
            }

            visit(s);
        }
    }

    // TODO: patch stack size (the push instruction)
    patchJumpAddresses();

    codeRefs.insert(_function, squeezeCode());

    qSwap(_currentStatement, cs);
    qSwap(_removableJumps, removableJumps);
    qSwap(_function, function);
    qSwap(block, _block);
    qSwap(nextBlock, _nextBlock);
    qSwap(patches, _patches);
    qSwap(addrs, _addrs);
    qSwap(codeStart, _codeStart);
    qSwap(codeNext, _codeNext);
    qSwap(codeEnd, _codeEnd);

    delete[] codeStart;
}

QQmlRefPointer<QV4::CompiledData::CompilationUnit> InstructionSelection::backendCompileStep()
{
    compilationUnit->codeRefs.resize(irModule->functions.size());
    int i = 0;
    for (IR::Function *irFunction : qAsConst(irModule->functions))
        compilationUnit->codeRefs[i++] = codeRefs[irFunction];
    QQmlRefPointer<QV4::CompiledData::CompilationUnit> result;
    result.adopt(compilationUnit.take());
    return result;
}

void InstructionSelection::callValue(IR::Expr *value, IR::ExprList *args, IR::Expr *result)
{
    Instruction::CallValue call;
    prepareCallArgs(args, call.argc);
    call.callData = callDataStart();
    call.dest = getParam(value);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callQmlContextProperty(IR::Expr *base, IR::Member::MemberKind kind, int propertyIndex, IR::ExprList *args, IR::Expr *result)
{
    if (kind == IR::Member::MemberOfQmlScopeObject) {
        Instruction::CallScopeObjectProperty call;
        call.base = getParam(base);
        call.index = propertyIndex;
        prepareCallArgs(args, call.argc);
        call.callData = callDataStart();
        call.result = getResultParam(result);
        addInstruction(call);
    } else if (kind == IR::Member::MemberOfQmlContextObject) {
        Instruction::CallContextObjectProperty call;
        call.base = getParam(base);
        call.index = propertyIndex;
        prepareCallArgs(args, call.argc);
        call.callData = callDataStart();
        call.result = getResultParam(result);
        addInstruction(call);
    } else {
        Q_ASSERT(false);
    }
}

void InstructionSelection::callProperty(IR::Expr *base, const QString &name, IR::ExprList *args,
                                        IR::Expr *result)
{
    if (useFastLookups) {
        Instruction::CallPropertyLookup call;
        call.base = getParam(base);
        call.lookupIndex = registerGetterLookup(name);
        prepareCallArgs(args, call.argc);
        call.callData = callDataStart();
        call.result = getResultParam(result);
        addInstruction(call);
    } else {
        // call the property on the loaded base
        Instruction::CallProperty call;
        call.base = getParam(base);
        call.name = registerString(name);
        prepareCallArgs(args, call.argc);
        call.callData = callDataStart();
        call.result = getResultParam(result);
        addInstruction(call);
    }
}

void InstructionSelection::callSubscript(IR::Expr *base, IR::Expr *index, IR::ExprList *args,
                                         IR::Expr *result)
{
    // call the property on the loaded base
    Instruction::CallElement call;
    call.base = getParam(base);
    call.index = getParam(index);
    prepareCallArgs(args, call.argc);
    call.callData = callDataStart();
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::convertType(IR::Expr *source, IR::Expr *target)
{
    // FIXME: do something more useful with this info
    if (target->type & IR::NumberType && !(source->type & IR::NumberType))
        unop(IR::OpUPlus, source, target);
    else
        copyValue(source, target);
}

void InstructionSelection::constructActivationProperty(IR::Name *func,
                                                       IR::ExprList *args,
                                                       IR::Expr *target)
{
    if (useFastLookups && func->global) {
        Instruction::ConstructGlobalLookup call;
        call.index = registerGlobalGetterLookup(*func->id);
        prepareCallArgs(args, call.argc);
        call.callData = callDataStart();
        call.result = getResultParam(target);
        addInstruction(call);
        return;
    }
    Instruction::CreateActivationProperty create;
    create.name = registerString(*func->id);
    prepareCallArgs(args, create.argc);
    create.callData = callDataStart();
    create.result = getResultParam(target);
    addInstruction(create);
}

void InstructionSelection::constructProperty(IR::Expr *base, const QString &name, IR::ExprList *args, IR::Expr *target)
{
    if (useFastLookups) {
        Instruction::ConstructPropertyLookup call;
        call.base = getParam(base);
        call.index = registerGetterLookup(name);
        prepareCallArgs(args, call.argc);
        call.callData = callDataStart();
        call.result = getResultParam(target);
        addInstruction(call);
        return;
    }
    Instruction::CreateProperty create;
    create.base = getParam(base);
    create.name = registerString(name);
    prepareCallArgs(args, create.argc);
    create.callData = callDataStart();
    create.result = getResultParam(target);
    addInstruction(create);
}

void InstructionSelection::constructValue(IR::Expr *value, IR::ExprList *args, IR::Expr *target)
{
    Instruction::CreateValue create;
    create.func = getParam(value);
    prepareCallArgs(args, create.argc);
    create.callData = callDataStart();
    create.result = getResultParam(target);
    addInstruction(create);
}

void InstructionSelection::loadThisObject(IR::Expr *e)
{
    Instruction::LoadThis load;
    load.result = getResultParam(e);
    addInstruction(load);
}

void InstructionSelection::loadQmlContext(IR::Expr *e)
{
    Instruction::LoadQmlContext load;
    load.result = getResultParam(e);
    addInstruction(load);
}

void InstructionSelection::loadQmlImportedScripts(IR::Expr *e)
{
    Instruction::LoadQmlImportedScripts load;
    load.result = getResultParam(e);
    addInstruction(load);
}

void InstructionSelection::loadQmlSingleton(const QString &name, IR::Expr *e)
{
    Instruction::LoadQmlSingleton load;
    load.result = getResultParam(e);
    load.name = registerString(name);
    addInstruction(load);
}

void InstructionSelection::loadConst(IR::Const *sourceConst, IR::Expr *e)
{
    Q_ASSERT(sourceConst);

    Instruction::MoveConst move;
    move.source = convertToValue(sourceConst).asReturnedValue();
    move.result = getResultParam(e);
    addInstruction(move);
}

void InstructionSelection::loadString(const QString &str, IR::Expr *target)
{
    Instruction::LoadRuntimeString load;
    load.stringId = registerString(str);
    load.result = getResultParam(target);
    addInstruction(load);
}

void InstructionSelection::loadRegexp(IR::RegExp *sourceRegexp, IR::Expr *target)
{
    Instruction::LoadRegExp load;
    load.regExpId = registerRegExp(sourceRegexp);
    load.result = getResultParam(target);
    addInstruction(load);
}

void InstructionSelection::getActivationProperty(const IR::Name *name, IR::Expr *target)
{
    if (useFastLookups && name->global) {
        Instruction::GetGlobalLookup load;
        load.index = registerGlobalGetterLookup(*name->id);
        load.result = getResultParam(target);
        addInstruction(load);
        return;
    }
    Instruction::LoadName load;
    load.name = registerString(*name->id);
    load.result = getResultParam(target);
    addInstruction(load);
}

void InstructionSelection::setActivationProperty(IR::Expr *source, const QString &targetName)
{
    Instruction::StoreName store;
    store.source = getParam(source);
    store.name = registerString(targetName);
    addInstruction(store);
}

void InstructionSelection::initClosure(IR::Closure *closure, IR::Expr *target)
{
    int id = closure->value;
    Instruction::LoadClosure load;
    load.value = id;
    load.result = getResultParam(target);
    addInstruction(load);
}

void InstructionSelection::getProperty(IR::Expr *base, const QString &name, IR::Expr *target)
{
    if (useFastLookups) {
        Instruction::GetLookup load;
        load.base = getParam(base);
        load.index = registerGetterLookup(name);
        load.result = getResultParam(target);
        addInstruction(load);
        return;
    }
    Instruction::LoadProperty load;
    load.base = getParam(base);
    load.name = registerString(name);
    load.result = getResultParam(target);
    addInstruction(load);
}

void InstructionSelection::setProperty(IR::Expr *source, IR::Expr *targetBase,
                                       const QString &targetName)
{
    if (useFastLookups) {
        Instruction::SetLookup store;
        store.base = getParam(targetBase);
        store.index = registerSetterLookup(targetName);
        store.source = getParam(source);
        addInstruction(store);
        return;
    }
    Instruction::StoreProperty store;
    store.base = getParam(targetBase);
    store.name = registerString(targetName);
    store.source = getParam(source);
    addInstruction(store);
}

void InstructionSelection::setQmlContextProperty(IR::Expr *source, IR::Expr *targetBase, IR::Member::MemberKind kind, int propertyIndex)
{
    if (kind == IR::Member::MemberOfQmlScopeObject) {
        Instruction::StoreScopeObjectProperty store;
        store.base = getParam(targetBase);
        store.propertyIndex = propertyIndex;
        store.source = getParam(source);
        addInstruction(store);
    } else if (kind == IR::Member::MemberOfQmlContextObject) {
        Instruction::StoreContextObjectProperty store;
        store.base = getParam(targetBase);
        store.propertyIndex = propertyIndex;
        store.source = getParam(source);
        addInstruction(store);
    } else {
        Q_ASSERT(false);
    }
}

void InstructionSelection::setQObjectProperty(IR::Expr *source, IR::Expr *targetBase, int propertyIndex)
{
    Instruction::StoreQObjectProperty store;
    store.base = getParam(targetBase);
    store.propertyIndex = propertyIndex;
    store.source = getParam(source);
    addInstruction(store);
}

void InstructionSelection::getQmlContextProperty(IR::Expr *source, IR::Member::MemberKind kind, int index, bool captureRequired, IR::Expr *target)
{
    if (kind == IR::Member::MemberOfQmlScopeObject) {
        Instruction::LoadScopeObjectProperty load;
        load.base = getParam(source);
        load.propertyIndex = index;
        load.captureRequired = captureRequired;
        load.result = getResultParam(target);
        addInstruction(load);
    } else if (kind == IR::Member::MemberOfQmlContextObject) {
        Instruction::LoadContextObjectProperty load;
        load.base = getParam(source);
        load.propertyIndex = index;
        load.captureRequired = captureRequired;
        load.result = getResultParam(target);
        addInstruction(load);
    } else if (kind == IR::Member::MemberOfIdObjectsArray) {
        Instruction::LoadIdObject load;
        load.base = getParam(source);
        load.index = index;
        load.result = getResultParam(target);
        addInstruction(load);
    } else {
        Q_ASSERT(false);
    }
}

void InstructionSelection::getQObjectProperty(IR::Expr *base, int propertyIndex, bool captureRequired, bool isSingletonProperty, int attachedPropertiesId, IR::Expr *target)
{
    if (attachedPropertiesId != 0) {
        Instruction::LoadAttachedQObjectProperty load;
        load.propertyIndex = propertyIndex;
        load.result = getResultParam(target);
        load.attachedPropertiesId = attachedPropertiesId;
        addInstruction(load);
    } else if (isSingletonProperty) {
        Instruction::LoadSingletonQObjectProperty load;
        load.base = getParam(base);
        load.propertyIndex = propertyIndex;
        load.result = getResultParam(target);
        load.captureRequired = captureRequired;
        addInstruction(load);
    } else {
        Instruction::LoadQObjectProperty load;
        load.base = getParam(base);
        load.propertyIndex = propertyIndex;
        load.result = getResultParam(target);
        load.captureRequired = captureRequired;
        addInstruction(load);
    }
}

void InstructionSelection::getElement(IR::Expr *base, IR::Expr *index, IR::Expr *target)
{
    if (0 && useFastLookups) {
        Instruction::LoadElementLookup load;
        load.lookup = registerIndexedGetterLookup();
        load.base = getParam(base);
        load.index = getParam(index);
        load.result = getResultParam(target);
        addInstruction(load);
        return;
    }
    Instruction::LoadElement load;
    load.base = getParam(base);
    load.index = getParam(index);
    load.result = getResultParam(target);
    addInstruction(load);
}

void InstructionSelection::setElement(IR::Expr *source, IR::Expr *targetBase,
                                      IR::Expr *targetIndex)
{
    if (0 && useFastLookups) {
        Instruction::StoreElementLookup store;
        store.lookup = registerIndexedSetterLookup();
        store.base = getParam(targetBase);
        store.index = getParam(targetIndex);
        store.source = getParam(source);
        addInstruction(store);
        return;
    }
    Instruction::StoreElement store;
    store.base = getParam(targetBase);
    store.index = getParam(targetIndex);
    store.source = getParam(source);
    addInstruction(store);
}

void InstructionSelection::copyValue(IR::Expr *source, IR::Expr *target)
{
    Instruction::Move move;
    move.source = getParam(source);
    move.result = getResultParam(target);
    if (move.source != move.result)
        addInstruction(move);
}

void InstructionSelection::swapValues(IR::Expr *source, IR::Expr *target)
{
    Instruction::SwapTemps swap;
    swap.left = getParam(source);
    swap.right = getParam(target);
    addInstruction(swap);
}

void InstructionSelection::unop(IR::AluOp oper, IR::Expr *source, IR::Expr *target)
{
    switch (oper) {
    case IR::OpIfTrue:
        Q_ASSERT(!"unreachable"); break;
    case IR::OpNot: {
        // ### enabling this fails in some cases, where apparently the value is not a bool at runtime
        if (0 && isBoolType(source)) {
            Instruction::UNotBool unot;
            unot.source = getParam(source);
            unot.result = getResultParam(target);
            addInstruction(unot);
            return;
        }
        Instruction::UNot unot;
        unot.source = getParam(source);
        unot.result = getResultParam(target);
        addInstruction(unot);
        return;
    }
    case IR::OpUMinus: {
        Instruction::UMinus uminus;
        uminus.source = getParam(source);
        uminus.result = getResultParam(target);
        addInstruction(uminus);
        return;
    }
    case IR::OpUPlus: {
        if (isNumberType(source)) {
            // use a move
            Instruction::Move move;
            move.source = getParam(source);
            move.result = getResultParam(target);
            if (move.source != move.result)
                addInstruction(move);
            return;
        }
        Instruction::UPlus uplus;
        uplus.source = getParam(source);
        uplus.result = getResultParam(target);
        addInstruction(uplus);
        return;
    }
    case IR::OpCompl: {
        // ### enabling this fails in some cases, where apparently the value is not a int at runtime
        if (0 && isIntegerType(source)) {
            Instruction::UComplInt unot;
            unot.source = getParam(source);
            unot.result = getResultParam(target);
            addInstruction(unot);
            return;
        }
        Instruction::UCompl ucompl;
        ucompl.source = getParam(source);
        ucompl.result = getResultParam(target);
        addInstruction(ucompl);
        return;
    }
    case IR::OpIncrement: {
        Instruction::Increment inc;
        inc.source = getParam(source);
        inc.result = getResultParam(target);
        addInstruction(inc);
        return;
    }
    case IR::OpDecrement: {
        Instruction::Decrement dec;
        dec.source = getParam(source);
        dec.result = getResultParam(target);
        addInstruction(dec);
        return;
    }
    default:  break;
    } // switch

    Q_ASSERT(!"unreachable");
}

void InstructionSelection::binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Expr *target)
{
    binopHelper(oper, leftSource, rightSource, target);
}

Param InstructionSelection::binopHelper(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Expr *target)
{
    if (oper == IR::OpAdd) {
        Instruction::Add add;
        add.lhs = getParam(leftSource);
        add.rhs = getParam(rightSource);
        add.result = getResultParam(target);
        addInstruction(add);
        return add.result;
    }
    if (oper == IR::OpSub) {
        Instruction::Sub sub;
        sub.lhs = getParam(leftSource);
        sub.rhs = getParam(rightSource);
        sub.result = getResultParam(target);
        addInstruction(sub);
        return sub.result;
    }
    if (oper == IR::OpMul) {
        Instruction::Mul mul;
        mul.lhs = getParam(leftSource);
        mul.rhs = getParam(rightSource);
        mul.result = getResultParam(target);
        addInstruction(mul);
        return mul.result;
    }
    if (oper == IR::OpBitAnd) {
        if (leftSource->asConst())
            qSwap(leftSource, rightSource);
        if (IR::Const *c = rightSource->asConst()) {
            Instruction::BitAndConst bitAnd;
            bitAnd.lhs = getParam(leftSource);
            bitAnd.rhs = convertToValue(c).Value::toInt32();
            bitAnd.result = getResultParam(target);
            addInstruction(bitAnd);
            return bitAnd.result;
        }
        Instruction::BitAnd bitAnd;
        bitAnd.lhs = getParam(leftSource);
        bitAnd.rhs = getParam(rightSource);
        bitAnd.result = getResultParam(target);
        addInstruction(bitAnd);
        return bitAnd.result;
    }
    if (oper == IR::OpBitOr) {
        if (leftSource->asConst())
            qSwap(leftSource, rightSource);
        if (IR::Const *c = rightSource->asConst()) {
            Instruction::BitOrConst bitOr;
            bitOr.lhs = getParam(leftSource);
            bitOr.rhs = convertToValue(c).Value::toInt32();
            bitOr.result = getResultParam(target);
            addInstruction(bitOr);
            return bitOr.result;
        }
        Instruction::BitOr bitOr;
        bitOr.lhs = getParam(leftSource);
        bitOr.rhs = getParam(rightSource);
        bitOr.result = getResultParam(target);
        addInstruction(bitOr);
        return bitOr.result;
    }
    if (oper == IR::OpBitXor) {
        if (leftSource->asConst())
            qSwap(leftSource, rightSource);
        if (IR::Const *c = rightSource->asConst()) {
            Instruction::BitXorConst bitXor;
            bitXor.lhs = getParam(leftSource);
            bitXor.rhs = convertToValue(c).Value::toInt32();
            bitXor.result = getResultParam(target);
            addInstruction(bitXor);
            return bitXor.result;
        }
        Instruction::BitXor bitXor;
        bitXor.lhs = getParam(leftSource);
        bitXor.rhs = getParam(rightSource);
        bitXor.result = getResultParam(target);
        addInstruction(bitXor);
        return bitXor.result;
    }
    if (oper == IR::OpRShift) {
        if (IR::Const *c = rightSource->asConst()) {
            Instruction::ShrConst shr;
            shr.lhs = getParam(leftSource);
            shr.rhs = convertToValue(c).Value::toInt32() & 0x1f;
            shr.result = getResultParam(target);
            addInstruction(shr);
            return shr.result;
        }
        Instruction::Shr shr;
        shr.lhs = getParam(leftSource);
        shr.rhs = getParam(rightSource);
        shr.result = getResultParam(target);
        addInstruction(shr);
        return shr.result;
    }
    if (oper == IR::OpLShift) {
        if (IR::Const *c = rightSource->asConst()) {
            Instruction::ShlConst shl;
            shl.lhs = getParam(leftSource);
            shl.rhs = convertToValue(c).Value::toInt32() & 0x1f;
            shl.result = getResultParam(target);
            addInstruction(shl);
            return shl.result;
        }
        Instruction::Shl shl;
        shl.lhs = getParam(leftSource);
        shl.rhs = getParam(rightSource);
        shl.result = getResultParam(target);
        addInstruction(shl);
        return shl.result;
    }

    if (oper == IR::OpInstanceof || oper == IR::OpIn || oper == IR::OpAdd) {
        Instruction::BinopContext binop;
        if (oper == IR::OpInstanceof)
            binop.alu = QV4::Runtime::instanceof;
        else if (oper == IR::OpIn)
            binop.alu = QV4::Runtime::in;
        else
            binop.alu = QV4::Runtime::add;
        binop.lhs = getParam(leftSource);
        binop.rhs = getParam(rightSource);
        binop.result = getResultParam(target);
        Q_ASSERT(binop.alu != QV4::Runtime::InvalidRuntimeMethod);
        addInstruction(binop);
        return binop.result;
    } else {
        auto binopFunc = aluOpFunction(oper);
        Q_ASSERT(binopFunc != QV4::Runtime::InvalidRuntimeMethod);
        Instruction::Binop binop;
        binop.alu = binopFunc;
        binop.lhs = getParam(leftSource);
        binop.rhs = getParam(rightSource);
        binop.result = getResultParam(target);
        addInstruction(binop);
        return binop.result;
    }
}

void InstructionSelection::prepareCallArgs(IR::ExprList *e, quint32 &argc, quint32 *args)
{
    int argLocation = outgoingArgumentTempStart();
    argc = 0;
    if (args)
        *args = argLocation;
    if (e) {
        // We need to move all the temps into the function arg array
        Q_ASSERT(argLocation >= 0);
        while (e) {
            if (IR::Const *c = e->expr->asConst()) {
                Instruction::MoveConst move;
                move.source = convertToValue(c).asReturnedValue();
                move.result = Param::createTemp(argLocation);
                addInstruction(move);
            } else {
                Instruction::Move move;
                move.source = getParam(e->expr);
                move.result = Param::createTemp(argLocation);
                addInstruction(move);
            }
            ++argLocation;
            ++argc;
            e = e->next;
        }
    }
}

void InstructionSelection::addDebugInstruction()
{
#ifndef QT_NO_QML_DEBUGGER
    if (blockNeedsDebugInstruction) {
        Instruction::Debug debug;
        debug.lineNumber = -int(currentLine);
        addInstruction(debug);
    }
#endif
}

void InstructionSelection::visitJump(IR::Jump *s)
{
    if (s->target == _nextBlock)
        return;
    if (_removableJumps.at(_block->index()))
        return;

    addDebugInstruction();

    Instruction::Jump jump;
    jump.offset = 0;
    ptrdiff_t loc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump));

    _patches[s->target].append(loc);
}

void InstructionSelection::visitCJump(IR::CJump *s)
{
    addDebugInstruction();

    Param condition;
    if (IR::Temp *t = s->cond->asTemp()) {
        condition = getResultParam(t);
    } else if (IR::Binop *b = s->cond->asBinop()) {
        condition = binopHelper(b->op, b->left, b->right, /*target*/0);
    } else {
        Q_UNIMPLEMENTED();
    }

    if (s->iftrue == _nextBlock) {
        Instruction::JumpNe jump;
        jump.offset = 0;
        jump.condition = condition;
        ptrdiff_t falseLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump));
        _patches[s->iffalse].append(falseLoc);
    } else {
        Instruction::JumpEq jump;
        jump.offset = 0;
        jump.condition = condition;
        ptrdiff_t trueLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump));
        _patches[s->iftrue].append(trueLoc);

        if (s->iffalse != _nextBlock) {
            Instruction::Jump jump;
            jump.offset = 0;
            ptrdiff_t falseLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump));
            _patches[s->iffalse].append(falseLoc);
        }
    }
}

void InstructionSelection::visitRet(IR::Ret *s)
{
    // this is required so stepOut will always be guaranteed to stop in every stack frame
    addDebugInstruction();

    Instruction::Ret ret;
    ret.result = getParam(s->expr);
    addInstruction(ret);
}

void InstructionSelection::callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Expr *result)
{
    if (useFastLookups && func->global) {
        Instruction::CallGlobalLookup call;
        call.index = registerGlobalGetterLookup(*func->id);
        prepareCallArgs(args, call.argc);
        call.callData = callDataStart();
        call.result = getResultParam(result);
        addInstruction(call);
        return;
    }
    Instruction::CallActivationProperty call;
    call.name = registerString(*func->id);
    prepareCallArgs(args, call.argc);
    call.callData = callDataStart();
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinTypeofQmlContextProperty(IR::Expr *base, IR::Member::MemberKind kind, int propertyIndex, IR::Expr *result)
{
    if (kind == IR::Member::MemberOfQmlScopeObject) {
        Instruction::CallBuiltinTypeofScopeObjectProperty call;
        call.base = getParam(base);
        call.index = propertyIndex;
        call.result = getResultParam(result);
        addInstruction(call);
    } else if (kind == IR::Member::MemberOfQmlContextObject) {
        Instruction::CallBuiltinTypeofContextObjectProperty call;
        call.base = getParam(base);
        call.index = propertyIndex;
        call.result = getResultParam(result);
        addInstruction(call);
    } else {
        Q_UNREACHABLE();
    }
}

void InstructionSelection::callBuiltinTypeofMember(IR::Expr *base, const QString &name,
                                                   IR::Expr *result)
{
    Instruction::CallBuiltinTypeofMember call;
    call.base = getParam(base);
    call.member = registerString(name);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinTypeofSubscript(IR::Expr *base, IR::Expr *index,
                                                      IR::Expr *result)
{
    Instruction::CallBuiltinTypeofSubscript call;
    call.base = getParam(base);
    call.index = getParam(index);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinTypeofName(const QString &name, IR::Expr *result)
{
    Instruction::CallBuiltinTypeofName call;
    call.name = registerString(name);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinTypeofValue(IR::Expr *value, IR::Expr *result)
{
    Instruction::CallBuiltinTypeofValue call;
    call.value = getParam(value);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinDeleteMember(IR::Expr *base, const QString &name, IR::Expr *result)
{
    Instruction::CallBuiltinDeleteMember call;
    call.base = getParam(base);
    call.member = registerString(name);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinDeleteSubscript(IR::Expr *base, IR::Expr *index,
                                                      IR::Expr *result)
{
    Instruction::CallBuiltinDeleteSubscript call;
    call.base = getParam(base);
    call.index = getParam(index);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinDeleteName(const QString &name, IR::Expr *result)
{
    Instruction::CallBuiltinDeleteName call;
    call.name = registerString(name);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinDeleteValue(IR::Expr *result)
{
    Instruction::MoveConst move;
    move.source = QV4::Encode(false);
    move.result = getResultParam(result);
    addInstruction(move);
}

void InstructionSelection::callBuiltinThrow(IR::Expr *arg)
{
    Instruction::CallBuiltinThrow call;
    call.arg = getParam(arg);
    addInstruction(call);
}

void InstructionSelection::callBuiltinReThrow()
{
    if (_block->catchBlock) {
        // jump to exception handler
        Instruction::Jump jump;
        jump.offset = 0;
        ptrdiff_t loc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump));

        _patches[_block->catchBlock].append(loc);
    } else {
        Instruction::Ret ret;
        int idx = jsUnitGenerator()->registerConstant(QV4::Encode::undefined());
        ret.result = Param::createConstant(idx);
        addInstruction(ret);
    }
}

void InstructionSelection::callBuiltinUnwindException(IR::Expr *result)
{
    Instruction::CallBuiltinUnwindException call;
    call.result = getResultParam(result);
    addInstruction(call);
}


void InstructionSelection::callBuiltinPushCatchScope(const QString &exceptionName)
{
    Instruction::CallBuiltinPushCatchScope call;
    call.name = registerString(exceptionName);
    addInstruction(call);
}

void InstructionSelection::callBuiltinForeachIteratorObject(IR::Expr *arg, IR::Expr *result)
{
    Instruction::CallBuiltinForeachIteratorObject call;
    call.arg = getParam(arg);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinForeachNextPropertyname(IR::Expr *arg, IR::Expr *result)
{
    Instruction::CallBuiltinForeachNextPropertyName call;
    call.arg = getParam(arg);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinPushWithScope(IR::Expr *arg)
{
    Instruction::CallBuiltinPushScope call;
    call.arg = getParam(arg);
    addInstruction(call);
}

void InstructionSelection::callBuiltinPopScope()
{
    QT_WARNING_PUSH
    QT_WARNING_DISABLE_GCC("-Wuninitialized")
    Instruction::CallBuiltinPopScope call;
    addInstruction(call);
    QT_WARNING_POP
}

void InstructionSelection::callBuiltinDeclareVar(bool deletable, const QString &name)
{
    Instruction::CallBuiltinDeclareVar call;
    call.isDeletable = deletable;
    call.varName = registerString(name);
    addInstruction(call);
}

void InstructionSelection::callBuiltinDefineArray(IR::Expr *result, IR::ExprList *args)
{
    Instruction::CallBuiltinDefineArray call;
    prepareCallArgs(args, call.argc, &call.args);
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinDefineObjectLiteral(IR::Expr *result, int keyValuePairCount, IR::ExprList *keyValuePairs, IR::ExprList *arrayEntries, bool needSparseArray)
{
    int argLocation = outgoingArgumentTempStart();

    const int classId = registerJSClass(keyValuePairCount, keyValuePairs);

    // Process key/value pairs first
    IR::ExprList *it = keyValuePairs;
    for (int i = 0; i < keyValuePairCount; ++i, it = it->next) {
        // Skip name
        it = it->next;

        bool isData = it->expr->asConst()->value;
        it = it->next;

        if (IR::Const *c = it->expr->asConst()) {
            Instruction::MoveConst move;
            move.source = convertToValue(c).asReturnedValue();
            move.result = Param::createTemp(argLocation);
            addInstruction(move);
        } else {
            Instruction::Move move;
            move.source = getParam(it->expr);
            move.result = Param::createTemp(argLocation);
            addInstruction(move);
        }
        ++argLocation;

        if (!isData) {
            it = it->next;

            Instruction::Move move;
            move.source = getParam(it->expr);
            move.result = Param::createTemp(argLocation);
            addInstruction(move);
            ++argLocation;
        }
    }

    // Process array values
    uint arrayValueCount = 0;
    it = arrayEntries;
    while (it) {
        IR::Const *index = it->expr->asConst();
        it = it->next;

        bool isData = it->expr->asConst()->value;
        it = it->next;

        if (!isData) {
            it = it->next; // getter
            it = it->next; // setter
            continue;
        }

        ++arrayValueCount;

        Instruction::MoveConst indexMove;
        indexMove.source = convertToValue(index).asReturnedValue();
        indexMove.result = Param::createTemp(argLocation);
        addInstruction(indexMove);
        ++argLocation;

        Instruction::Move move;
        move.source = getParam(it->expr);
        move.result = Param::createTemp(argLocation);
        addInstruction(move);
        ++argLocation;
        it = it->next;
    }

    // Process array getter/setter pairs
    uint arrayGetterSetterCount = 0;
    it = arrayEntries;
    while (it) {
        IR::Const *index = it->expr->asConst();
        it = it->next;

        bool isData = it->expr->asConst()->value;
        it = it->next;

        if (isData) {
            it = it->next; // value
            continue;
        }

        ++arrayGetterSetterCount;

        Instruction::MoveConst indexMove;
        indexMove.source = convertToValue(index).asReturnedValue();
        indexMove.result = Param::createTemp(argLocation);
        addInstruction(indexMove);
        ++argLocation;

        // getter
        Instruction::Move moveGetter;
        moveGetter.source = getParam(it->expr);
        moveGetter.result = Param::createTemp(argLocation);
        addInstruction(moveGetter);
        ++argLocation;
        it = it->next;

        // setter
        Instruction::Move moveSetter;
        moveSetter.source = getParam(it->expr);
        moveSetter.result = Param::createTemp(argLocation);
        addInstruction(moveSetter);
        ++argLocation;
        it = it->next;
    }

    Instruction::CallBuiltinDefineObjectLiteral call;
    call.internalClassId = classId;
    call.arrayValueCount = arrayValueCount;
    call.arrayGetterSetterCountAndFlags = arrayGetterSetterCount | (needSparseArray << 30);
    call.args = outgoingArgumentTempStart();
    call.result = getResultParam(result);
    addInstruction(call);
}

void InstructionSelection::callBuiltinSetupArgumentObject(IR::Expr *result)
{
    Instruction::CallBuiltinSetupArgumentsObject call;
    call.result = getResultParam(result);
    addInstruction(call);
}


void QV4::Moth::InstructionSelection::callBuiltinConvertThisToObject()
{
    QT_WARNING_PUSH
    QT_WARNING_DISABLE_GCC("-Wuninitialized")
    Instruction::CallBuiltinConvertThisToObject call;
    addInstruction(call);
    QT_WARNING_POP
}

ptrdiff_t InstructionSelection::addInstructionHelper(Instr::Type type, Instr &instr)
{
    instr.common.instructionType = type;

    int instructionSize = Instr::size(type);
    if (_codeEnd - _codeNext < instructionSize) {
        int currSize = _codeEnd - _codeStart;
        uchar *newCode = new uchar[currSize * 2];
        ::memset(newCode + currSize, 0, currSize);
        ::memcpy(newCode, _codeStart, currSize);
        _codeNext = _codeNext - _codeStart + newCode;
        delete[] _codeStart;
        _codeStart = newCode;
        _codeEnd = _codeStart + currSize * 2;
    }

    ::memcpy(_codeNext, reinterpret_cast<const char *>(&instr), instructionSize);
    ptrdiff_t ptrOffset = _codeNext - _codeStart;
    _codeNext += instructionSize;

    return ptrOffset;
}

void InstructionSelection::patchJumpAddresses()
{
    typedef QHash<IR::BasicBlock *, QVector<ptrdiff_t> >::ConstIterator PatchIt;
    for (PatchIt i = _patches.cbegin(), ei = _patches.cend(); i != ei; ++i) {
        Q_ASSERT(_addrs.contains(i.key()));
        ptrdiff_t target = _addrs.value(i.key());

        const QVector<ptrdiff_t> &patchList = i.value();
        for (int ii = 0, eii = patchList.count(); ii < eii; ++ii) {
            ptrdiff_t patch = patchList.at(ii);

            *((ptrdiff_t *)(_codeStart + patch)) = target - patch;
        }
    }

    _patches.clear();
    _addrs.clear();
}

QByteArray InstructionSelection::squeezeCode() const
{
    int codeSize = _codeNext - _codeStart;
    QByteArray squeezed;
    squeezed.resize(codeSize);
    ::memcpy(squeezed.data(), _codeStart, codeSize);
    return squeezed;
}

Param InstructionSelection::getParam(IR::Expr *e) {
    Q_ASSERT(e);

    if (IR::Const *c = e->asConst()) {
        int idx = jsUnitGenerator()->registerConstant(convertToValue(c).asReturnedValue());
        return Param::createConstant(idx);
    } else if (IR::Temp *t = e->asTemp()) {
        switch (t->kind) {
        case IR::Temp::StackSlot:
            return Param::createTemp(t->index);
        default:
            Q_UNREACHABLE();
            return Param();
        }
    } else if (IR::ArgLocal *al = e->asArgLocal()) {
        switch (al->kind) {
        case IR::ArgLocal::Formal:
        case IR::ArgLocal::ScopedFormal: return Param::createArgument(al->index, al->scope);
        case IR::ArgLocal::Local: return Param::createLocal(al->index);
        case IR::ArgLocal::ScopedLocal: return Param::createScopedLocal(al->index, al->scope);
        default:
            Q_UNREACHABLE();
            return Param();
        }
    } else {
        Q_UNIMPLEMENTED();
        return Param();
    }
}


CompilationUnit::~CompilationUnit()
{
}

#if !defined(V4_BOOTSTRAP)

void CompilationUnit::linkBackendToEngine(QV4::ExecutionEngine *engine)
{
    runtimeFunctions.resize(data->functionTableSize);
    runtimeFunctions.fill(0);
    for (int i = 0 ;i < runtimeFunctions.size(); ++i) {
        const QV4::CompiledData::Function *compiledFunction = data->functionAt(i);

        QV4::Function *runtimeFunction = new QV4::Function(engine, this, compiledFunction, &VME::exec);
        runtimeFunction->codeData = reinterpret_cast<const uchar *>(codeRefs.at(i).constData());
        runtimeFunctions[i] = runtimeFunction;
    }
}

bool CompilationUnit::memoryMapCode(QString *errorString)
{
    Q_UNUSED(errorString);
    codeRefs.resize(data->functionTableSize);

    const char *basePtr = reinterpret_cast<const char *>(data);

    for (uint i = 0; i < data->functionTableSize; ++i) {
        const CompiledData::Function *compiledFunction = data->functionAt(i);
        const char *codePtr = const_cast<const char *>(reinterpret_cast<const char *>(basePtr + compiledFunction->codeOffset));
        QByteArray code = QByteArray::fromRawData(codePtr, compiledFunction->codeSize);
        codeRefs[i] = code;
    }

    return true;
}

#endif // V4_BOOTSTRAP

void CompilationUnit::prepareCodeOffsetsForDiskStorage(CompiledData::Unit *unit)
{
    const int codeAlignment = 16;
    quint64 offset = WTF::roundUpToMultipleOf(codeAlignment, unit->unitSize);
    Q_ASSERT(int(unit->functionTableSize) == codeRefs.size());
    for (int i = 0; i < codeRefs.size(); ++i) {
        CompiledData::Function *compiledFunction = const_cast<CompiledData::Function *>(unit->functionAt(i));
        compiledFunction->codeOffset = offset;
        compiledFunction->codeSize = codeRefs.at(i).size();
        offset = WTF::roundUpToMultipleOf(codeAlignment, offset + compiledFunction->codeSize);
    }
}

bool CompilationUnit::saveCodeToDisk(QIODevice *device, const CompiledData::Unit *unit, QString *errorString)
{
    Q_ASSERT(device->pos() == unit->unitSize);
    Q_ASSERT(device->atEnd());
    Q_ASSERT(int(unit->functionTableSize) == codeRefs.size());

    QByteArray padding;

    for (int i = 0; i < codeRefs.size(); ++i) {
        const CompiledData::Function *compiledFunction = unit->functionAt(i);

        if (device->pos() > qint64(compiledFunction->codeOffset)) {
            *errorString = QStringLiteral("Invalid state of cache file to write.");
            return false;
        }

        const quint64 paddingSize = compiledFunction->codeOffset - device->pos();
        padding.fill(0, paddingSize);
        qint64 written = device->write(padding);
        if (written != padding.size()) {
            *errorString = device->errorString();
            return false;
        }

        QByteArray code = codeRefs.at(i);

        written = device->write(code.constData(), compiledFunction->codeSize);
        if (written != qint64(compiledFunction->codeSize)) {
            *errorString = device->errorString();
            return false;
        }
    }
    return true;
}

QQmlRefPointer<CompiledData::CompilationUnit> ISelFactory::createUnitForLoading()
{
    QQmlRefPointer<CompiledData::CompilationUnit> result;
    result.adopt(new Moth::CompilationUnit);
    return result;
}
