/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QBuffer>
#include <QFile>

#include "qv4engine_p.h"
#include "qv4baselineassembler_p.h"
#include "qv4assemblercommon_p.h"
#include <private/qv4function_p.h>
#include <private/qv4runtime_p.h>
#include <private/qv4stackframe_p.h>

#include <wtf/Vector.h>
#include <assembler/MacroAssembler.h>
#include <assembler/MacroAssemblerCodeRef.h>
#include <assembler/LinkBuffer.h>
#include <WTFStubs.h>

#undef ENABLE_ALL_ASSEMBLERS_FOR_REFACTORING_PURPOSES

#if QT_CONFIG(qml_jit)

QT_BEGIN_NAMESPACE
namespace QV4 {
namespace JIT {

#define ASM_GENERATE_RUNTIME_CALL(function, destination) \
    pasm()->GENERATE_RUNTIME_CALL(function, destination)
#define callHelper(x) \
    PlatformAssemblerCommon::callRuntimeUnchecked(reinterpret_cast<void *>(&x), #x)

const QV4::Value::ValueTypeInternal IntegerTag = QV4::Value::ValueTypeInternal::Integer;

static ReturnedValue toNumberHelper(ReturnedValue v)
{
    return Encode(Value::fromReturnedValue(v).toNumber());
}

static ReturnedValue toInt32Helper(ReturnedValue v)
{
    return Encode(Value::fromReturnedValue(v).toInt32());
}

#if QT_POINTER_SIZE == 8 || defined(ENABLE_ALL_ASSEMBLERS_FOR_REFACTORING_PURPOSES)
class PlatformAssembler64 : public PlatformAssemblerCommon
{
public:
    PlatformAssembler64(const Value *constantTable)
        : PlatformAssemblerCommon(constantTable)
    {}

    void callRuntime(const void *funcPtr, CallResultDestination dest)
    {
        PlatformAssemblerCommon::callRuntime(funcPtr);
        if (dest == CallResultDestination::InAccumulator)
            saveReturnValueInAccumulator();
        else if (AccumulatorRegister == ReturnValueRegister)
            loadUndefined();
    }

    void saveReturnValueInAccumulator()
    {
        move(ReturnValueRegister, AccumulatorRegister);
    }

    void loadUndefined(RegisterID dest = AccumulatorRegister)
    {
        move(TrustedImm64(0), dest);
    }

    void copyConst(int constIndex, Address dest)
    {
        //###
        if (constant(constIndex).isUndefined()) {
            loadUndefined(ScratchRegister);
        } else {
            load64(loadConstAddress(constIndex, ScratchRegister), ScratchRegister);
        }
        store64(ScratchRegister, dest);
    }

    void copyReg(Address src, Address dst)
    {
        load64(src, ScratchRegister);
        store64(ScratchRegister, dst);
    }

    void loadPointerFromValue(Address addr, RegisterID dest = AccumulatorRegister)
    {
        load64(addr, dest);
    }

    void loadAccumulator(Address addr)
    {
        load64(addr, AccumulatorRegister);
    }

    void storeAccumulator(Address addr)
    {
        store64(AccumulatorRegister, addr);
    }

    void moveReg(Address sourceRegAddress, Address destRegAddress)
    {
        load64(sourceRegAddress, ScratchRegister);
        store64(ScratchRegister, destRegAddress);
    }

    void loadString(int stringId)
    {
        loadAccumulator(loadStringAddress(stringId));
    }

    void loadValue(ReturnedValue value)
    {
        move(TrustedImm64(value), AccumulatorRegister);
    }

    void storeHeapObject(RegisterID source, Address addr)
    {
        store64(source, addr);
    }

    void generateCatchTrampoline()
    {
        PlatformAssemblerCommon::generateCatchTrampoline([this](){loadUndefined();});
    }

    void jumpNotUndefined(int offset)
    {
        auto jump = branch64(NotEqual, AccumulatorRegister, TrustedImm64(0));
        addJumpToOffset(jump, offset);
    }

    Jump jumpEmpty()
    {
        return branch64(Equal, AccumulatorRegister, TrustedImm64(Value::emptyValue().asReturnedValue()));
    }

    Jump jumpNotEmpty()
    {
        return branch64(NotEqual, AccumulatorRegister, TrustedImm64(Value::emptyValue().asReturnedValue()));
    }

    void toBoolean(std::function<void(RegisterID)> continuation)
    {
        urshift64(AccumulatorRegister, TrustedImm32(Value::IsIntegerConvertible_Shift), ScratchRegister);
        auto needsConversion = branch32(NotEqual, TrustedImm32(1), ScratchRegister);
        continuation(AccumulatorRegister);
        Jump done = jump();

        // slow path:
        needsConversion.link(this);
        push(AccumulatorRegister);
        move(AccumulatorRegister, registerForArg(0));
        callHelper(Value::toBooleanImpl);
        and32(TrustedImm32(1), ReturnValueRegister, ScratchRegister);
        pop(AccumulatorRegister);
        continuation(ScratchRegister);

        done.link(this);
    }

    void toNumber()
    {
        urshift64(AccumulatorRegister, TrustedImm32(Value::QuickType_Shift), ScratchRegister);
        auto isNumber = branch32(GreaterThanOrEqual, ScratchRegister, TrustedImm32(Value::QT_Int));

        move(AccumulatorRegister, registerForArg(0));
        callHelper(toNumberHelper);
        saveReturnValueInAccumulator();

        isNumber.link(this);
    }

    // this converts both the lhs and the accumulator to int32
    void toInt32LhsAcc(Address lhs, RegisterID lhsTarget)
    {
        load64(lhs, lhsTarget);
        urshift64(lhsTarget, TrustedImm32(Value::QuickType_Shift), ScratchRegister2);
        auto lhsIsInt = branch32(Equal, TrustedImm32(Value::QT_Int), ScratchRegister2);

        const Address accumulatorStackAddress(JSStackFrameRegister,
                                              offsetof(CallData, accumulator));
        storeAccumulator(accumulatorStackAddress);
        move(lhsTarget, registerForArg(0));
        callHelper(toInt32Helper);
        move(ReturnValueRegister, lhsTarget);
        loadAccumulator(accumulatorStackAddress);

        lhsIsInt.link(this);
        urshift64(AccumulatorRegister, TrustedImm32(Value::QuickType_Shift), ScratchRegister2);
        auto isInt = branch32(Equal, TrustedImm32(Value::QT_Int), ScratchRegister2);

        pushAligned(lhsTarget);
        move(AccumulatorRegister, registerForArg(0));
        callHelper(toInt32Helper);
        saveReturnValueInAccumulator();
        popAligned(lhsTarget);

        isInt.link(this);
    }

    void toInt32()
    {
        urshift64(AccumulatorRegister, TrustedImm32(Value::QuickType_Shift), ScratchRegister2);
        auto isInt = branch32(Equal, TrustedImm32(Value::QT_Int), ScratchRegister2);

        move(AccumulatorRegister, registerForArg(0));
        callHelper(toInt32Helper);
        saveReturnValueInAccumulator();

        isInt.link(this);
    }

    void regToInt32(Address srcReg, RegisterID targetReg)
    {
        load64(srcReg, targetReg);
        urshift64(targetReg, TrustedImm32(Value::QuickType_Shift), ScratchRegister2);
        auto isInt = branch32(Equal, TrustedImm32(Value::QT_Int), ScratchRegister2);

        pushAligned(AccumulatorRegister);
        move(targetReg, registerForArg(0));
        callHelper(toInt32Helper);
        move(ReturnValueRegister, targetReg);
        popAligned(AccumulatorRegister);

        isInt.link(this);
    }

    void isNullOrUndefined()
    {
        move(AccumulatorRegister, ScratchRegister);
        compare64(Equal, ScratchRegister, TrustedImm32(0), AccumulatorRegister);
        Jump isUndef = branch32(NotEqual, TrustedImm32(0), AccumulatorRegister);

        // not undefined
        rshift64(TrustedImm32(32), ScratchRegister);
        compare32(Equal, ScratchRegister, TrustedImm32(int(QV4::Value::ValueTypeInternal::Null)),
                  AccumulatorRegister);

        isUndef.link(this);
    }

    Jump isIntOrBool()
    {
        urshift64(AccumulatorRegister, TrustedImm32(Value::IsIntegerOrBool_Shift), ScratchRegister);
        return branch32(Equal, TrustedImm32(3), ScratchRegister);
    }

    void jumpStrictEqualStackSlotInt(int lhs, int rhs, int offset)
    {
        Address lhsAddr(JSStackFrameRegister, lhs * int(sizeof(Value)));
        load64(lhsAddr, ScratchRegister);
        Jump isUndef = branch64(Equal, ScratchRegister, TrustedImm64(0));
        Jump equal = branch32(Equal, TrustedImm32(rhs), ScratchRegister);
        addJumpToOffset(equal, offset);
        isUndef.link(this);
    }

    void jumpStrictNotEqualStackSlotInt(int lhs, int rhs, int offset)
    {
        Address lhsAddr(JSStackFrameRegister, lhs * int(sizeof(Value)));
        load64(lhsAddr, ScratchRegister);
        Jump isUndef = branch64(Equal, ScratchRegister, TrustedImm64(0));
        addJumpToOffset(isUndef, offset);
        Jump notEqual = branch32(NotEqual, TrustedImm32(rhs), ScratchRegister);
        addJumpToOffset(notEqual, offset);
    }

    void setAccumulatorTag(QV4::Value::ValueTypeInternal tag, RegisterID sourceReg = NoRegister)
    {
        if (sourceReg == NoRegister)
            or64(TrustedImm64(int64_t(tag) << 32), AccumulatorRegister);
        else
            or64(TrustedImm64(int64_t(tag) << 32), sourceReg, AccumulatorRegister);
    }

    void encodeDoubleIntoAccumulator(FPRegisterID src)
    {
        moveDoubleTo64(src, AccumulatorRegister);
        move(TrustedImm64(Value::NaNEncodeMask), ScratchRegister);
        xor64(ScratchRegister, AccumulatorRegister);
    }

    void pushValueAligned(ReturnedValue v)
    {
        loadValue(v);
        pushAligned(AccumulatorRegister);
    }

    void popValueAligned()
    {
        addPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
    }

    Jump binopBothIntPath(Address lhsAddr, std::function<Jump(void)> fastPath)
    {
        urshift64(AccumulatorRegister, TrustedImm32(32), ScratchRegister);
        Jump accNotInt = branch32(NotEqual, TrustedImm32(int(IntegerTag)), ScratchRegister);
        load64(lhsAddr, ScratchRegister);
        urshift64(ScratchRegister, TrustedImm32(32), ScratchRegister2);
        Jump lhsNotInt = branch32(NotEqual, TrustedImm32(int(IntegerTag)), ScratchRegister2);

        // both integer
        Jump failure = fastPath();
        Jump done = jump();

        // all other cases
        if (failure.isSet())
            failure.link(this);
        accNotInt.link(this);
        lhsNotInt.link(this);

        return done;
    }

    Jump unopIntPath(std::function<Jump(void)> fastPath)
    {
        urshift64(AccumulatorRegister, TrustedImm32(Value::IsIntegerConvertible_Shift), ScratchRegister);
        Jump accNotIntConvertible = branch32(NotEqual, TrustedImm32(1), ScratchRegister);

        // both integer
        Jump failure = fastPath();
        Jump done = jump();

        // all other cases
        if (failure.isSet())
            failure.link(this);
        accNotIntConvertible.link(this);

        return done;
    }

    void callWithAccumulatorByValueAsFirstArgument(std::function<void()> doCall)
    {
        passAsArg(AccumulatorRegister, 0);
        doCall();
    }
};

typedef PlatformAssembler64 PlatformAssembler;
#endif

#if QT_POINTER_SIZE == 4 || defined(ENABLE_ALL_ASSEMBLERS_FOR_REFACTORING_PURPOSES)
class PlatformAssembler32 : public PlatformAssemblerCommon
{
public:
    PlatformAssembler32(const Value *constantTable)
        : PlatformAssemblerCommon(constantTable)
    {}

    void callRuntime(const void *funcPtr, CallResultDestination dest)
    {
        PlatformAssemblerCommon::callRuntime(funcPtr);
        if (dest == CallResultDestination::InAccumulator)
            saveReturnValueInAccumulator();
        else if (AccumulatorRegisterValue == ReturnValueRegisterValue)
            loadUndefined();
    }

    void saveReturnValueInAccumulator()
    {
        move(ReturnValueRegisterValue, AccumulatorRegisterValue);
        move(ReturnValueRegisterTag, AccumulatorRegisterTag);
    }

    void loadUndefined()
    {
        move(TrustedImm32(0), AccumulatorRegisterValue);
        move(TrustedImm32(0), AccumulatorRegisterTag);
    }

    void copyConst(int constIndex, Address destRegAddr)
    {
        //###
        if (constant(constIndex).isUndefined()) {
            move(TrustedImm32(0), ScratchRegister);
            store32(ScratchRegister, destRegAddr);
            destRegAddr.offset += 4;
            store32(ScratchRegister, destRegAddr);
        } else {
            Address src = loadConstAddress(constIndex);
            loadDouble(src, FPScratchRegister);
            storeDouble(FPScratchRegister, destRegAddr);
        }
    }

    void copyReg(Address src, Address dest)
    {
        loadDouble(src, FPScratchRegister);
        storeDouble(FPScratchRegister, dest);
    }

    void loadPointerFromValue(Address addr, RegisterID dest = AccumulatorRegisterValue)
    {
        load32(addr, dest);
    }

    void loadAccumulator(Address src)
    {
        load32(src, AccumulatorRegisterValue);
        src.offset += 4;
        load32(src, AccumulatorRegisterTag);
    }

    void storeAccumulator(Address addr)
    {
        store32(AccumulatorRegisterValue, addr);
        addr.offset += 4;
        store32(AccumulatorRegisterTag, addr);
    }

    void moveReg(Address sourceRegAddress, Address destRegAddress)
    {
        load32(sourceRegAddress, ScratchRegister);
        store32(ScratchRegister, destRegAddress);
        sourceRegAddress.offset += 4;
        destRegAddress.offset += 4;
        load32(sourceRegAddress, ScratchRegister);
        store32(ScratchRegister, destRegAddress);
    }

    void loadString(int stringId)
    {
        load32(loadStringAddress(stringId), AccumulatorRegisterValue);
        move(TrustedImm32(0), AccumulatorRegisterTag);
    }

    void loadValue(ReturnedValue value)
    {
        move(TrustedImm32(Value::fromReturnedValue(value).value()), AccumulatorRegisterValue);
        move(TrustedImm32(Value::fromReturnedValue(value).tag()), AccumulatorRegisterTag);
    }

    void storeHeapObject(RegisterID source, Address addr)
    {
        store32(source, addr);
        addr.offset += 4;
        store32(TrustedImm32(0), addr);
    }


    void generateCatchTrampoline()
    {
        PlatformAssemblerCommon::generateCatchTrampoline([this](){loadUndefined();});
    }

    void toNumber()
    {
        urshift32(AccumulatorRegisterTag, TrustedImm32(Value::QuickType_Shift - 32), ScratchRegister);
        auto isNumber = branch32(GreaterThanOrEqual, ScratchRegister, TrustedImm32(Value::QT_Int));

        if (ArgInRegCount < 2) {
            subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister); // stack alignment
            push(AccumulatorRegisterTag);
            push(AccumulatorRegisterValue);
        } else {
            move(AccumulatorRegisterValue, registerForArg(0));
            move(AccumulatorRegisterTag, registerForArg(1));
        }
        callHelper(toNumberHelper);
        saveReturnValueInAccumulator();
        if (ArgInRegCount < 2)
            addPtr(TrustedImm32(4 * PointerSize), StackPointerRegister);

        isNumber.link(this);
    }

    // this converts both the lhs and the accumulator to int32
    void toInt32LhsAcc(Address lhs, RegisterID lhsTarget)
    {
        bool accumulatorNeedsSaving = AccumulatorRegisterValue == ReturnValueRegisterValue
                || AccumulatorRegisterTag == ReturnValueRegisterTag;
        lhs.offset += 4;
        load32(lhs, lhsTarget);
        lhs.offset -= 4;
        auto lhsIsNotInt = branch32(NotEqual, TrustedImm32(int(IntegerTag)), lhsTarget);
        load32(lhs, lhsTarget);
        auto lhsIsInt = jump();

        lhsIsNotInt.link(this);

        // Save accumulator from being garbage collected, no matter if we will reuse the register.
        const Address accumulatorStackAddress(JSStackFrameRegister,
                                              offsetof(CallData, accumulator));
        storeAccumulator(accumulatorStackAddress);

        if (ArgInRegCount < 2) {
            subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
            push(lhsTarget);
            load32(lhs, lhsTarget);
            push(lhsTarget);
        } else {
            move(lhsTarget, registerForArg(1));
            load32(lhs, registerForArg(0));
        }
        callHelper(toInt32Helper);
        move(ReturnValueRegisterValue, lhsTarget);
        if (ArgInRegCount < 2)
            addPtr(TrustedImm32(4 * PointerSize), StackPointerRegister);

        if (accumulatorNeedsSaving) // otherwise it's still the same
            loadAccumulator(accumulatorStackAddress);

        lhsIsInt.link(this);

        auto rhsIsInt = branch32(Equal, TrustedImm32(int(IntegerTag)), AccumulatorRegisterTag);

        pushAligned(lhsTarget);
        if (ArgInRegCount < 2) {
            subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
            push(AccumulatorRegisterTag);
            push(AccumulatorRegisterValue);
        } else {
            move(AccumulatorRegisterValue, registerForArg(0));
            move(AccumulatorRegisterTag, registerForArg(1));
        }
        callHelper(toInt32Helper);
        saveReturnValueInAccumulator();
        if (ArgInRegCount < 2)
            addPtr(TrustedImm32(4 * PointerSize), StackPointerRegister);
        popAligned(lhsTarget);

        rhsIsInt.link(this);
    }

    void toInt32()
    {
        urshift32(AccumulatorRegisterTag, TrustedImm32(Value::QuickType_Shift - 32), ScratchRegister);
        auto isInt = branch32(Equal, TrustedImm32(Value::QT_Int), ScratchRegister);

        if (ArgInRegCount < 2) {
            subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister); // align the stack on a 16-byte boundary
            push(AccumulatorRegisterTag);
            push(AccumulatorRegisterValue);
        } else {
            move(AccumulatorRegisterValue, registerForArg(0));
            move(AccumulatorRegisterTag, registerForArg(1));
        }
        callHelper(toInt32Helper);
        saveReturnValueInAccumulator();
        if (ArgInRegCount < 2)
            addPtr(TrustedImm32(4 * PointerSize), StackPointerRegister);

        isInt.link(this);
    }

    void regToInt32(Address srcReg, RegisterID targetReg)
    {
        bool accumulatorNeedsSaving = AccumulatorRegisterValue == ReturnValueRegisterValue
                || AccumulatorRegisterTag == ReturnValueRegisterTag;
        if (accumulatorNeedsSaving) {
            push(AccumulatorRegisterTag);
            push(AccumulatorRegisterValue);
        }
        if (ArgInRegCount < 2) {
            if (!accumulatorNeedsSaving)
                subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
            srcReg.offset += 4;
            load32(srcReg, targetReg);
            push(targetReg);
            srcReg.offset -= 4;
            load32(srcReg, targetReg);
            push(targetReg);
        } else {
            if (accumulatorNeedsSaving)
                subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
            load32(srcReg, registerForArg(0));
            srcReg.offset += 4;
            load32(srcReg, registerForArg(1));
        }
        callHelper(toInt32Helper);
        move(ReturnValueRegisterValue, targetReg);
        if (accumulatorNeedsSaving) {
            addPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
            pop(AccumulatorRegisterValue);
            pop(AccumulatorRegisterTag);
        } else if (ArgInRegCount < 2) {
            addPtr(TrustedImm32(4 * PointerSize), StackPointerRegister);
        }
    }

    void isNullOrUndefined()
    {
        Jump notUndefOrPtr = branch32(NotEqual, TrustedImm32(0), AccumulatorRegisterTag);
        compare32(Equal, AccumulatorRegisterValue, TrustedImm32(0), AccumulatorRegisterValue);
        auto done = jump();

        // not undefined or managed
        notUndefOrPtr.link(this);
        compare32(Equal, AccumulatorRegisterTag, TrustedImm32(int(QV4::Value::ValueTypeInternal::Null)),
                  AccumulatorRegisterValue);

        done.link(this);
    }

    Jump isIntOrBool()
    {
        urshift32(AccumulatorRegisterTag, TrustedImm32(Value::IsIntegerOrBool_Shift - 32), ScratchRegister);
        return branch32(Equal, TrustedImm32(3), ScratchRegister);
    }

    void pushValue(ReturnedValue v)
    {
        push(TrustedImm32(v >> 32));
        push(TrustedImm32(v));
    }

    void jumpNotUndefined(int offset)
    {
        move(AccumulatorRegisterTag, ScratchRegister);
        or32(AccumulatorRegisterValue, ScratchRegister);
        auto jump = branch32(NotEqual, ScratchRegister, TrustedImm32(0));
        addJumpToOffset(jump, offset);
    }

    Jump jumpEmpty()
    {
        return branch32(Equal, AccumulatorRegisterTag, TrustedImm32(Value::emptyValue().asReturnedValue() >> 32));
    }

    Jump jumpNotEmpty()
    {
        return branch32(NotEqual, AccumulatorRegisterTag, TrustedImm32(Value::emptyValue().asReturnedValue() >> 32));
    }

    void toBoolean(std::function<void(RegisterID)> continuation)
    {
        urshift32(AccumulatorRegisterTag, TrustedImm32(Value::IsIntegerConvertible_Shift - 32),
                  ScratchRegister);
        auto needsConversion = branch32(NotEqual, TrustedImm32(1), ScratchRegister);
        continuation(AccumulatorRegisterValue);
        Jump done = jump();

        // slow path:
        needsConversion.link(this);

        bool accumulatorNeedsSaving = AccumulatorRegisterValue == ReturnValueRegisterValue
                || AccumulatorRegisterTag == ReturnValueRegisterTag;
        if (accumulatorNeedsSaving) {
            push(AccumulatorRegisterTag);
            push(AccumulatorRegisterValue);
        }

        if (ArgInRegCount < 2) {
            if (!accumulatorNeedsSaving)
                subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
            push(AccumulatorRegisterTag);
            push(AccumulatorRegisterValue);
        } else {
            if (accumulatorNeedsSaving)
                subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
            move(AccumulatorRegisterValue, registerForArg(0));
            move(AccumulatorRegisterTag, registerForArg(1));
        }
        callHelper(Value::toBooleanImpl);
        and32(TrustedImm32(1), ReturnValueRegisterValue, ScratchRegister);
        if (accumulatorNeedsSaving) {
            addPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
            pop(AccumulatorRegisterValue);
            pop(AccumulatorRegisterTag);
        } else if (ArgInRegCount < 2) {
            addPtr(TrustedImm32(4 * PointerSize), StackPointerRegister);
        }
        continuation(ScratchRegister);

        done.link(this);
    }

    void jumpStrictEqualStackSlotInt(int lhs, int rhs, int offset)
    {
        Address lhsAddr(JSStackFrameRegister, lhs * int(sizeof(Value)));
        load32(lhsAddr, ScratchRegister);
        Jump notEqInt = branch32(NotEqual, ScratchRegister, TrustedImm32(rhs));
        Jump notEqUndefVal = branch32(NotEqual, ScratchRegister, TrustedImm32(0));
        addJumpToOffset(notEqUndefVal, offset);
        lhsAddr.offset += 4;
        load32(lhsAddr, ScratchRegister);
        Jump notEqUndefTag = branch32(NotEqual, ScratchRegister, TrustedImm32(0));
        addJumpToOffset(notEqUndefTag, offset);
        notEqInt.link(this);
    }

    void jumpStrictNotEqualStackSlotInt(int lhs, int rhs, int offset)
    {
        Address lhsAddr(JSStackFrameRegister, lhs * int(sizeof(Value)));
        load32(lhsAddr, ScratchRegister);
        Jump notEqual = branch32(NotEqual, TrustedImm32(rhs), ScratchRegister);
        addJumpToOffset(notEqual, offset);
        Jump notUndefValue = branch32(NotEqual, TrustedImm32(0), ScratchRegister);
        lhsAddr.offset += 4;
        load32(lhsAddr, ScratchRegister);
        Jump equalUndef = branch32(Equal, TrustedImm32(0), ScratchRegister);
        addJumpToOffset(equalUndef, offset);
        notUndefValue.link(this);
    }

    void setAccumulatorTag(QV4::Value::ValueTypeInternal tag, RegisterID sourceReg = NoRegister)
    {
        if (sourceReg != NoRegister)
            move(sourceReg, AccumulatorRegisterValue);
        move(TrustedImm32(int(tag)), AccumulatorRegisterTag);
    }

    void encodeDoubleIntoAccumulator(FPRegisterID src)
    {
        moveDoubleToInts(src, AccumulatorRegisterValue, AccumulatorRegisterTag);
        xor32(TrustedImm32(Value::NaNEncodeMask >> 32), AccumulatorRegisterTag);
    }

    void pushValueAligned(ReturnedValue v)
    {
        pushValue(v);
    }

    void popValueAligned()
    {
        popValue();
    }

    Jump binopBothIntPath(Address lhsAddr, std::function<Jump(void)> fastPath)
    {
        Jump accNotInt = branch32(NotEqual, TrustedImm32(int(IntegerTag)), AccumulatorRegisterTag);
        Address lhsAddrTag = lhsAddr; lhsAddrTag.offset += Value::tagOffset();
        load32(lhsAddrTag, ScratchRegister);
        Jump lhsNotInt = branch32(NotEqual, TrustedImm32(int(IntegerTag)), ScratchRegister);

        // both integer
        Address lhsAddrValue = lhsAddr; lhsAddrValue.offset += Value::valueOffset();
        load32(lhsAddrValue, ScratchRegister);
        Jump failure = fastPath();
        Jump done = jump();

        // all other cases
        if (failure.isSet())
            failure.link(this);
        accNotInt.link(this);
        lhsNotInt.link(this);

        return done;
    }

    Jump unopIntPath(std::function<Jump(void)> fastPath)
    {
        Jump accNotInt = branch32(NotEqual, TrustedImm32(int(IntegerTag)), AccumulatorRegisterTag);

        // both integer
        Jump failure = fastPath();
        Jump done = jump();

        // all other cases
        if (failure.isSet())
            failure.link(this);
        accNotInt.link(this);

        return done;
    }

    void callWithAccumulatorByValueAsFirstArgument(std::function<void()> doCall)
    {
        if (ArgInRegCount < 2) {
            subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister);
            push(AccumulatorRegisterTag);
            push(AccumulatorRegisterValue);
        } else {
            move(AccumulatorRegisterValue, registerForArg(0));
            move(AccumulatorRegisterTag, registerForArg(1));
        }
        doCall();
        if (ArgInRegCount < 2)
            addPtr(TrustedImm32(4 * PointerSize), StackPointerRegister);
    }
};

typedef PlatformAssembler32 PlatformAssembler;
#endif

#define pasm() reinterpret_cast<PlatformAssembler *>(this->d)

typedef PlatformAssembler::TrustedImmPtr TrustedImmPtr;
typedef PlatformAssembler::TrustedImm32 TrustedImm32;
typedef PlatformAssembler::TrustedImm64 TrustedImm64;
typedef PlatformAssembler::Address Address;
typedef PlatformAssembler::RegisterID RegisterID;
typedef PlatformAssembler::FPRegisterID FPRegisterID;

static Address regAddr(int reg)
{
    return Address(PlatformAssembler::JSStackFrameRegister, reg * int(sizeof(QV4::Value)));
}

BaselineAssembler::BaselineAssembler(const Value *constantTable)
    : d(new PlatformAssembler(constantTable))
{
}

BaselineAssembler::~BaselineAssembler()
{
    delete pasm();
}

void BaselineAssembler::generatePrologue()
{
    pasm()->generateFunctionEntry();
}

void BaselineAssembler::generateEpilogue()
{
    pasm()->generateCatchTrampoline();
}

void BaselineAssembler::link(Function *function)
{
    pasm()->link(function, "BaselineJIT");
}

void BaselineAssembler::addLabel(int offset)
{
    pasm()->addLabelForOffset(offset);
}

void BaselineAssembler::loadConst(int constIndex)
{
    //###
    if (pasm()->constant(constIndex).isUndefined()) {
        pasm()->loadUndefined();
    } else {
        pasm()->loadAccumulator(pasm()->loadConstAddress(constIndex));
    }
}

void BaselineAssembler::copyConst(int constIndex, int destReg)
{
    pasm()->copyConst(constIndex, regAddr(destReg));
}

void BaselineAssembler::loadReg(int reg)
{
    pasm()->loadAccumulator(regAddr(reg));
}

void JIT::BaselineAssembler::moveReg(int sourceReg, int destReg)
{
    pasm()->moveReg(regAddr(sourceReg), regAddr(destReg));
}

void BaselineAssembler::storeReg(int reg)
{
    pasm()->storeAccumulator(regAddr(reg));
}

void BaselineAssembler::loadLocal(int index, int level)
{
    Heap::CallContext ctx;
    Q_UNUSED(ctx)
    pasm()->loadPointerFromValue(regAddr(CallData::Context), PlatformAssembler::ScratchRegister);
    while (level) {
        pasm()->loadPtr(Address(PlatformAssembler::ScratchRegister, ctx.outer.offset), PlatformAssembler::ScratchRegister);
        --level;
    }
    pasm()->loadAccumulator(Address(PlatformAssembler::ScratchRegister, ctx.locals.offset + offsetof(ValueArray<0>, values) + sizeof(Value)*index));
}

void BaselineAssembler::storeLocal(int index, int level)
{
    Heap::CallContext ctx;
    Q_UNUSED(ctx)
    pasm()->loadPtr(regAddr(CallData::Context), PlatformAssembler::ScratchRegister);
    while (level) {
        pasm()->loadPtr(Address(PlatformAssembler::ScratchRegister, ctx.outer.offset), PlatformAssembler::ScratchRegister);
        --level;
    }
    pasm()->storeAccumulator(Address(PlatformAssembler::ScratchRegister, ctx.locals.offset + offsetof(ValueArray<0>, values) + sizeof(Value)*index));
}

void BaselineAssembler::loadString(int stringId)
{
    pasm()->loadString(stringId);
}

void BaselineAssembler::loadValue(ReturnedValue value)
{
    pasm()->loadValue(value);
}

void BaselineAssembler::storeHeapObject(int reg)
{
    pasm()->storeHeapObject(PlatformAssembler::ReturnValueRegisterValue, regAddr(reg));
}

void BaselineAssembler::loadImport(int index)
{
    Address addr = pasm()->loadCompilationUnitPtr(PlatformAssembler::ScratchRegister);
    addr.offset = offsetof(QV4::CompiledData::CompilationUnitBase, imports);
    pasm()->loadPtr(addr, PlatformAssembler::ScratchRegister);
    addr.offset = index * int(sizeof(QV4::Value*));
    pasm()->loadPtr(addr, PlatformAssembler::ScratchRegister);
    pasm()->loadAccumulator(Address(PlatformAssembler::ScratchRegister));
}

void BaselineAssembler::toNumber()
{
    pasm()->toNumber();
}

void BaselineAssembler::uminus()
{
    saveAccumulatorInFrame();
    pasm()->prepareCallWithArgCount(1);
    pasm()->passAccumulatorAsArg(0);
    ASM_GENERATE_RUNTIME_CALL(UMinus, CallResultDestination::InAccumulator);
    checkException();
}

void BaselineAssembler::ucompl()
{
    pasm()->toInt32();
    pasm()->xor32(TrustedImm32(-1), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

static ReturnedValue incHelper(const Value v)
{
    double d;
    if (Q_LIKELY(v.isDouble()))
        d =  v.doubleValue();
    else
        d = v.toNumberImpl();
    return Encode(d + 1.);
}

void BaselineAssembler::inc()
{
    auto done = pasm()->unopIntPath([this](){
        auto overflowed = pasm()->branchAdd32(PlatformAssembler::Overflow,
                                              PlatformAssembler::AccumulatorRegisterValue,
                                              TrustedImm32(1),
                                              PlatformAssembler::ScratchRegister);
        pasm()->setAccumulatorTag(IntegerTag, PlatformAssembler::ScratchRegister);
        return overflowed;
    });

    // slow path:
    pasm()->callWithAccumulatorByValueAsFirstArgument([this]() {
        pasm()->callHelper(incHelper);
        pasm()->saveReturnValueInAccumulator();
    });
    checkException();

    // done.
    done.link(pasm());
}

static ReturnedValue decHelper(const Value v)
{
    double d;
    if (Q_LIKELY(v.isDouble()))
        d =  v.doubleValue();
    else
        d = v.toNumberImpl();
    return Encode(d - 1.);
}

void BaselineAssembler::dec()
{
    auto done = pasm()->unopIntPath([this](){
        auto overflowed = pasm()->branchSub32(PlatformAssembler::Overflow,
                                              PlatformAssembler::AccumulatorRegisterValue,
                                              TrustedImm32(1),
                                              PlatformAssembler::ScratchRegister);
        pasm()->setAccumulatorTag(IntegerTag, PlatformAssembler::ScratchRegister);
        return overflowed;
    });

    // slow path:
    pasm()->callWithAccumulatorByValueAsFirstArgument([this]() {
        pasm()->callHelper(decHelper);
        pasm()->saveReturnValueInAccumulator();
    });
    checkException();

    // done.
    done.link(pasm());
}

void BaselineAssembler::unot()
{
    pasm()->toBoolean([this](PlatformAssembler::RegisterID resultReg){
        pasm()->compare32(PlatformAssembler::Equal, resultReg,
                          TrustedImm32(0), PlatformAssembler::AccumulatorRegisterValue);
        pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean);
    });
}

void BaselineAssembler::add(int lhs)
{
    auto done = pasm()->binopBothIntPath(regAddr(lhs), [this](){
        auto overflowed = pasm()->branchAdd32(PlatformAssembler::Overflow,
                                              PlatformAssembler::AccumulatorRegisterValue,
                                              PlatformAssembler::ScratchRegister);
        pasm()->setAccumulatorTag(IntegerTag,
                                  PlatformAssembler::ScratchRegister);
        return overflowed;
    });

    // slow path:
    saveAccumulatorInFrame();
    pasm()->prepareCallWithArgCount(3);
    pasm()->passAccumulatorAsArg(2);
    pasm()->passJSSlotAsArg(lhs, 1);
    pasm()->passEngineAsArg(0);
    ASM_GENERATE_RUNTIME_CALL(Add, CallResultDestination::InAccumulator);
    checkException();

    // done.
    done.link(pasm());
}

void BaselineAssembler::bitAnd(int lhs)
{
    PlatformAssembler::Address lhsAddr = regAddr(lhs);
    pasm()->toInt32LhsAcc(lhsAddr, PlatformAssembler::ScratchRegister);
    pasm()->and32(PlatformAssembler::ScratchRegister, PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::bitOr(int lhs)
{
    PlatformAssembler::Address lhsAddr = regAddr(lhs);
    pasm()->toInt32LhsAcc(lhsAddr, PlatformAssembler::ScratchRegister);
    pasm()->or32(PlatformAssembler::ScratchRegister, PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::bitXor(int lhs)
{
    PlatformAssembler::Address lhsAddr = regAddr(lhs);
    pasm()->toInt32LhsAcc(lhsAddr, PlatformAssembler::ScratchRegister);
    pasm()->xor32(PlatformAssembler::ScratchRegister, PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::ushr(int lhs)
{
    PlatformAssembler::Address lhsAddr = regAddr(lhs);
    pasm()->toInt32LhsAcc(lhsAddr, PlatformAssembler::ScratchRegister);
    pasm()->and32(TrustedImm32(0x1f), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->urshift32(PlatformAssembler::AccumulatorRegisterValue, PlatformAssembler::ScratchRegister);
    pasm()->move(PlatformAssembler::ScratchRegister, PlatformAssembler::AccumulatorRegisterValue);
    auto doubleEncode = pasm()->branch32(PlatformAssembler::LessThan,
                                         PlatformAssembler::AccumulatorRegisterValue,
                                         TrustedImm32(0));
    pasm()->setAccumulatorTag(IntegerTag);
    auto done = pasm()->jump();

    doubleEncode.link(pasm());
    pasm()->convertUInt32ToDouble(PlatformAssembler::AccumulatorRegisterValue,
                                  PlatformAssembler::FPScratchRegister,
                                  PlatformAssembler::ScratchRegister);
    pasm()->encodeDoubleIntoAccumulator(PlatformAssembler::FPScratchRegister);
    done.link(pasm());
}

void BaselineAssembler::shr(int lhs)
{
    PlatformAssembler::Address lhsAddr = regAddr(lhs);
    pasm()->toInt32LhsAcc(lhsAddr, PlatformAssembler::ScratchRegister);
    pasm()->and32(TrustedImm32(0x1f), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->rshift32(PlatformAssembler::AccumulatorRegisterValue, PlatformAssembler::ScratchRegister);
    pasm()->move(PlatformAssembler::ScratchRegister, PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::shl(int lhs)
{
    PlatformAssembler::Address lhsAddr = regAddr(lhs);
    pasm()->toInt32LhsAcc(lhsAddr, PlatformAssembler::ScratchRegister);
    pasm()->and32(TrustedImm32(0x1f), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->lshift32(PlatformAssembler::AccumulatorRegisterValue, PlatformAssembler::ScratchRegister);
    pasm()->move(PlatformAssembler::ScratchRegister, PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::bitAndConst(int rhs)
{
    pasm()->toInt32();
    pasm()->and32(TrustedImm32(rhs), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::bitOrConst(int rhs)
{
    pasm()->toInt32();
    pasm()->or32(TrustedImm32(rhs), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::bitXorConst(int rhs)
{
    pasm()->toInt32();
    pasm()->xor32(TrustedImm32(rhs), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::ushrConst(int rhs)
{
    rhs &= 0x1f;
    pasm()->toInt32();
    if (rhs) {
        // a non zero shift will always give a number encodable as an int
        pasm()->urshift32(TrustedImm32(rhs), PlatformAssembler::AccumulatorRegisterValue);
        pasm()->setAccumulatorTag(IntegerTag);
    } else {
        // shift with 0 can lead to a negative result
        auto doubleEncode = pasm()->branch32(PlatformAssembler::LessThan,
                                             PlatformAssembler::AccumulatorRegisterValue,
                                             TrustedImm32(0));
        pasm()->setAccumulatorTag(IntegerTag);
        auto done = pasm()->jump();

        doubleEncode.link(pasm());
        pasm()->convertUInt32ToDouble(PlatformAssembler::AccumulatorRegisterValue,
                                      PlatformAssembler::FPScratchRegister,
                                      PlatformAssembler::ScratchRegister);
        pasm()->encodeDoubleIntoAccumulator(PlatformAssembler::FPScratchRegister);
        done.link(pasm());
    }
}

void BaselineAssembler::shrConst(int rhs)
{
    rhs &= 0x1f;
    pasm()->toInt32();
    if (rhs)
        pasm()->rshift32(TrustedImm32(rhs), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::shlConst(int rhs)
{
    rhs &= 0x1f;
    pasm()->toInt32();
    if (rhs)
        pasm()->lshift32(TrustedImm32(rhs), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(IntegerTag);
}

void BaselineAssembler::mul(int lhs)
{
    auto done = pasm()->binopBothIntPath(regAddr(lhs), [this](){
        auto overflowed = pasm()->branchMul32(PlatformAssembler::Overflow,
                                              PlatformAssembler::AccumulatorRegisterValue,
                                              PlatformAssembler::ScratchRegister);
        pasm()->setAccumulatorTag(IntegerTag,
                                  PlatformAssembler::ScratchRegister);
        return overflowed;
    });

    // slow path:
    saveAccumulatorInFrame();
    pasm()->prepareCallWithArgCount(2);
    pasm()->passAccumulatorAsArg(1);
    pasm()->passJSSlotAsArg(lhs, 0);
    ASM_GENERATE_RUNTIME_CALL(Mul, CallResultDestination::InAccumulator);
    checkException();

    // done.
    done.link(pasm());
}

void BaselineAssembler::div(int lhs)
{
    saveAccumulatorInFrame();
    pasm()->prepareCallWithArgCount(2);
    pasm()->passAccumulatorAsArg(1);
    pasm()->passJSSlotAsArg(lhs, 0);
    ASM_GENERATE_RUNTIME_CALL(Div, CallResultDestination::InAccumulator);
    checkException();
}

void BaselineAssembler::mod(int lhs)
{
    saveAccumulatorInFrame();
    pasm()->prepareCallWithArgCount(2);
    pasm()->passAccumulatorAsArg(1);
    pasm()->passJSSlotAsArg(lhs, 0);
    ASM_GENERATE_RUNTIME_CALL(Mod, CallResultDestination::InAccumulator);
    checkException();
}

void BaselineAssembler::sub(int lhs)
{
    auto done = pasm()->binopBothIntPath(regAddr(lhs), [this](){
        auto overflowed = pasm()->branchSub32(PlatformAssembler::Overflow,
                                              PlatformAssembler::AccumulatorRegisterValue,
                                              PlatformAssembler::ScratchRegister);
        pasm()->setAccumulatorTag(IntegerTag,
                                  PlatformAssembler::ScratchRegister);
        return overflowed;
    });

    // slow path:
    saveAccumulatorInFrame();
    pasm()->prepareCallWithArgCount(2);
    pasm()->passAccumulatorAsArg(1);
    pasm()->passJSSlotAsArg(lhs, 0);
    ASM_GENERATE_RUNTIME_CALL(Sub, CallResultDestination::InAccumulator);
    checkException();

    // done.
    done.link(pasm());
}

void BaselineAssembler::cmpeqNull()
{
    pasm()->isNullOrUndefined();
    pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean);
}

void BaselineAssembler::cmpneNull()
{
    pasm()->isNullOrUndefined();
    pasm()->xor32(TrustedImm32(1), PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean);
}

void BaselineAssembler::cmpeqInt(int lhs)
{
    auto isIntOrBool = pasm()->isIntOrBool();
    saveAccumulatorInFrame();
    pasm()->pushValueAligned(Encode(lhs));
    if (PlatformAssembler::ArgInRegCount < 2)
        pasm()->push(PlatformAssembler::StackPointerRegister);
    else
        pasm()->move(PlatformAssembler::StackPointerRegister, pasm()->registerForArg(1));
    pasm()->pushAccumulatorAsArg(0);
    pasm()->callRuntimeUnchecked((void*)Runtime::Equal::call);
    pasm()->saveReturnValueInAccumulator();
    if (PlatformAssembler::ArgInRegCount < 2)
        pasm()->addPtr(TrustedImm32(2 * PlatformAssembler::PointerSize), PlatformAssembler::StackPointerRegister);
    pasm()->popValueAligned();
    auto done = pasm()->jump();
    isIntOrBool.link(pasm());
    pasm()->compare32(PlatformAssembler::Equal, PlatformAssembler::AccumulatorRegisterValue,
                      TrustedImm32(lhs),
                      PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean);
    done.link(pasm());
}

void BaselineAssembler::cmpneInt(int lhs)
{
    auto isIntOrBool = pasm()->isIntOrBool();
    saveAccumulatorInFrame();
    pasm()->pushValueAligned(Encode(lhs));
    if (PlatformAssembler::ArgInRegCount < 2)
        pasm()->push(PlatformAssembler::StackPointerRegister);
    else
        pasm()->move(PlatformAssembler::StackPointerRegister, pasm()->registerForArg(1));
    pasm()->pushAccumulatorAsArg(0);
    pasm()->callRuntimeUnchecked((void*)Runtime::NotEqual::call);
    pasm()->saveReturnValueInAccumulator();
    if (PlatformAssembler::ArgInRegCount < 2)
        pasm()->addPtr(TrustedImm32(2 * PlatformAssembler::PointerSize), PlatformAssembler::StackPointerRegister);
    pasm()->popValueAligned();
    auto done = pasm()->jump();
    isIntOrBool.link(pasm());
    pasm()->compare32(PlatformAssembler::NotEqual, PlatformAssembler::AccumulatorRegisterValue,
                      TrustedImm32(lhs),
                      PlatformAssembler::AccumulatorRegisterValue);
    pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean);
    done.link(pasm());
}

void BaselineAssembler::cmp(int cond, CmpFunc function, int lhs)
{
    auto c = static_cast<PlatformAssembler::RelationalCondition>(cond);
    auto done = pasm()->binopBothIntPath(regAddr(lhs), [this, c](){
        pasm()->compare32(c, PlatformAssembler::ScratchRegister,
                          PlatformAssembler::AccumulatorRegisterValue,
                          PlatformAssembler::AccumulatorRegisterValue);
        return PlatformAssembler::Jump();
    });

    // slow path:
    saveAccumulatorInFrame();
    pasm()->prepareCallWithArgCount(2);
    pasm()->passAccumulatorAsArg(1);
    pasm()->passJSSlotAsArg(lhs, 0);

    callRuntime(reinterpret_cast<void*>(function), CallResultDestination::InAccumulator);
    checkException();

    // done.
    done.link(pasm());
    pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean);
}

void BaselineAssembler::cmpeq(int lhs)
{
    cmp(PlatformAssembler::Equal, &Runtime::CompareEqual::call, lhs);
}

void BaselineAssembler::cmpne(int lhs)
{
    cmp(PlatformAssembler::NotEqual, &Runtime::CompareNotEqual::call, lhs);
}

void BaselineAssembler::cmpgt(int lhs)
{
    cmp(PlatformAssembler::GreaterThan, &Runtime::CompareGreaterThan::call, lhs);
}

void BaselineAssembler::cmpge(int lhs)
{
    cmp(PlatformAssembler::GreaterThanOrEqual, &Runtime::CompareGreaterEqual::call, lhs);
}

void BaselineAssembler::cmplt(int lhs)
{
    cmp(PlatformAssembler::LessThan, &Runtime::CompareLessThan::call, lhs);
}

void BaselineAssembler::cmple(int lhs)
{
    cmp(PlatformAssembler::LessThanOrEqual, &Runtime::CompareLessEqual::call, lhs);
}

void BaselineAssembler::cmpStrictEqual(int lhs)
{
    cmp(PlatformAssembler::Equal, &Runtime::CompareStrictEqual::call, lhs);
}

void BaselineAssembler::cmpStrictNotEqual(int lhs)
{
    cmp(PlatformAssembler::NotEqual, &Runtime::CompareStrictNotEqual::call, lhs);
}

int BaselineAssembler::jump(int offset)
{
    pasm()->addJumpToOffset(pasm()->jump(), offset);
    return offset;
}

int BaselineAssembler::jumpTrue(int offset)
{
    pasm()->toBoolean([this, offset](PlatformAssembler::RegisterID resultReg) {
        auto jump = pasm()->branch32(PlatformAssembler::NotEqual, TrustedImm32(0), resultReg);
        pasm()->addJumpToOffset(jump, offset);
    });
    return offset;
}

int BaselineAssembler::jumpFalse(int offset)
{
    pasm()->toBoolean([this, offset](PlatformAssembler::RegisterID resultReg) {
        auto jump = pasm()->branch32(PlatformAssembler::Equal, TrustedImm32(0), resultReg);
        pasm()->addJumpToOffset(jump, offset);
    });
    return offset;
}

int BaselineAssembler::jumpNoException(int offset)
{
    auto jump = pasm()->branch32(
        PlatformAssembler::Equal,
        PlatformAssembler::Address(PlatformAssembler::EngineRegister,
                                   offsetof(EngineBase, hasException)),
        TrustedImm32(0));
    pasm()->addJumpToOffset(jump, offset);
    return offset;
}

int BaselineAssembler::jumpNotUndefined(int offset)
{
    pasm()->jumpNotUndefined(offset);
    return offset;
}

void BaselineAssembler::prepareCallWithArgCount(int argc)
{
    pasm()->prepareCallWithArgCount(argc);
}

void BaselineAssembler::storeInstructionPointer(int instructionOffset)
{
    pasm()->storeInstructionPointer(instructionOffset);
}

void BaselineAssembler::passAccumulatorAsArg(int arg)
{
    pasm()->passAccumulatorAsArg(arg);
}

void BaselineAssembler::passFunctionAsArg(int arg)
{
    pasm()->passFunctionAsArg(arg);
}

void BaselineAssembler::passEngineAsArg(int arg)
{
    pasm()->passEngineAsArg(arg);
}

void BaselineAssembler::passJSSlotAsArg(int reg, int arg)
{
    pasm()->passJSSlotAsArg(reg, arg);
}

void BaselineAssembler::passCppFrameAsArg(int arg)
{
    pasm()->passCppFrameAsArg(arg);
}

void BaselineAssembler::passInt32AsArg(int value, int arg)
{
    pasm()->passInt32AsArg(value, arg);
}

void BaselineAssembler::passPointerAsArg(void *ptr, int arg)
{
    pasm()->passPointerAsArg(ptr, arg);
}

void BaselineAssembler::callRuntime(const void *funcPtr, CallResultDestination dest)
{
    pasm()->callRuntime(funcPtr, dest);
}

void BaselineAssembler::saveAccumulatorInFrame()
{
    pasm()->storeAccumulator(PlatformAssembler::Address(PlatformAssembler::JSStackFrameRegister,
                                                        offsetof(CallData, accumulator)));
}

void BaselineAssembler::loadAccumulatorFromFrame()
{
    pasm()->loadAccumulator(PlatformAssembler::Address(PlatformAssembler::JSStackFrameRegister,
                                                       offsetof(CallData, accumulator)));
}

static ReturnedValue TheJitIs__Tail_Calling__ToTheRuntimeSoTheJitFrameIsMissing(CppStackFrame *frame, ExecutionEngine *engine)
{
    return Runtime::TailCall::call(frame, engine);
}

void BaselineAssembler::jsTailCall(int func, int thisObject, int argc, int argv)
{
    Address tos = pasm()->jsAlloca(4);

    int32_t argcOffset = tos.offset + int32_t(sizeof(Value)) * Runtime::StackOffsets::tailCall_argc;
    int32_t argvOffset = tos.offset + int32_t(sizeof(Value)) * Runtime::StackOffsets::tailCall_argv;
    int32_t thisOffset = tos.offset + int32_t(sizeof(Value)) * Runtime::StackOffsets::tailCall_thisObject;
    int32_t funcOffset = tos.offset + int32_t(sizeof(Value)) * Runtime::StackOffsets::tailCall_function;

    pasm()->storeInt32AsValue(argc,      Address(tos.base, argcOffset));
    pasm()->storeInt32AsValue(argv,      Address(tos.base, argvOffset));
    pasm()->moveReg(regAddr(thisObject), Address(tos.base, thisOffset));
    pasm()->moveReg(regAddr(func),       Address(tos.base, funcOffset));
    pasm()->tailCallRuntime(
            reinterpret_cast<void *>(TheJitIs__Tail_Calling__ToTheRuntimeSoTheJitFrameIsMissing),
            "TheJitIs__Tail_Calling__ToTheRuntimeSoTheJitFrameIsMissing");
}

void BaselineAssembler::checkException()
{
    pasm()->checkException();
}

void BaselineAssembler::gotoCatchException()
{
    pasm()->addCatchyJump(pasm()->jump());
}

void BaselineAssembler::getException()
{
    Q_STATIC_ASSERT(sizeof(QV4::EngineBase::hasException) == 1);

    Address hasExceptionAddr(PlatformAssembler::EngineRegister,
                             offsetof(EngineBase, hasException));
    PlatformAssembler::Jump nope = pasm()->branch8(PlatformAssembler::Equal,
                                                  hasExceptionAddr,
                                                  TrustedImm32(0));
    pasm()->loadPtr(Address(PlatformAssembler::EngineRegister,
                            offsetof(EngineBase, exceptionValue)),
                    PlatformAssembler::ScratchRegister);
    pasm()->loadAccumulator(Address(PlatformAssembler::ScratchRegister));
    pasm()->store8(TrustedImm32(0), hasExceptionAddr);
    auto done = pasm()->jump();
    nope.link(pasm());
    pasm()->loadValue(Value::emptyValue().asReturnedValue());

    done.link(pasm());
}

void BaselineAssembler::setException()
{
    auto noException = pasm()->jumpEmpty();
    Address addr(PlatformAssembler::EngineRegister, offsetof(EngineBase, exceptionValue));
    pasm()->loadPtr(addr, PlatformAssembler::ScratchRegister);
    pasm()->storeAccumulator(Address(PlatformAssembler::ScratchRegister));
    addr.offset = offsetof(EngineBase, hasException);
    Q_STATIC_ASSERT(sizeof(QV4::EngineBase::hasException) == 1);
    pasm()->store8(TrustedImm32(1), addr);
    noException.link(pasm());
}

int BaselineAssembler::setUnwindHandler(int offset)
{
    auto l = pasm()->storePtrWithPatch(TrustedImmPtr(nullptr), pasm()->exceptionHandlerAddress());
    pasm()->addEHTarget(l, offset);
    return offset;
}


void BaselineAssembler::clearUnwindHandler()
{
    pasm()->storePtr(TrustedImmPtr(nullptr), pasm()->exceptionHandlerAddress());
}

void JIT::BaselineAssembler::unwindDispatch()
{
    checkException();
    pasm()->load32(Address(PlatformAssembler::CppStackFrameRegister, offsetof(CppStackFrame, unwindLevel)), PlatformAssembler::ScratchRegister);
    auto noUnwind = pasm()->branch32(PlatformAssembler::Equal, PlatformAssembler::ScratchRegister, TrustedImm32(0));
    pasm()->sub32(TrustedImm32(1), PlatformAssembler::ScratchRegister);
    pasm()->store32(PlatformAssembler::ScratchRegister, Address(PlatformAssembler::CppStackFrameRegister, offsetof(CppStackFrame, unwindLevel)));
    auto jump = pasm()->branch32(PlatformAssembler::Equal, PlatformAssembler::ScratchRegister, TrustedImm32(0));
    gotoCatchException();
    jump.link(pasm());

    pasm()->loadPtr(Address(PlatformAssembler::CppStackFrameRegister, offsetof(CppStackFrame, unwindLabel)), PlatformAssembler::ScratchRegister);
    pasm()->jump(PlatformAssembler::ScratchRegister);

    noUnwind.link(pasm());
}

int JIT::BaselineAssembler::unwindToLabel(int level, int offset)
{
    auto l = pasm()->storePtrWithPatch(TrustedImmPtr(nullptr), Address(PlatformAssembler::CppStackFrameRegister, offsetof(CppStackFrame, unwindLabel)));
    pasm()->addEHTarget(l, offset);
    pasm()->store32(TrustedImm32(level), Address(PlatformAssembler::CppStackFrameRegister, offsetof(CppStackFrame, unwindLevel)));
    gotoCatchException();
    return offset;
}

void BaselineAssembler::pushCatchContext(int index, int name)
{
    pasm()->prepareCallWithArgCount(3);
    pasm()->passInt32AsArg(name, 2);
    pasm()->passInt32AsArg(index, 1);
    pasm()->passEngineAsArg(0);
    ASM_GENERATE_RUNTIME_CALL(PushCatchContext, CallResultDestination::Ignore);
}

void BaselineAssembler::popContext()
{
    Heap::CallContext ctx;
    Q_UNUSED(ctx)
    pasm()->loadPointerFromValue(regAddr(CallData::Context), PlatformAssembler::ScratchRegister);
    pasm()->loadPtr(Address(PlatformAssembler::ScratchRegister, ctx.outer.offset), PlatformAssembler::ScratchRegister);
    pasm()->storeHeapObject(PlatformAssembler::ScratchRegister, regAddr(CallData::Context));
}

void BaselineAssembler::deadTemporalZoneCheck(int offsetForSavedIP, int variableName)
{
    auto valueIsAliveJump = pasm()->jumpNotEmpty();
    storeInstructionPointer(offsetForSavedIP);
    prepareCallWithArgCount(2);
    passInt32AsArg(variableName, 1);
    passEngineAsArg(0);
    ASM_GENERATE_RUNTIME_CALL(ThrowReferenceError, CallResultDestination::Ignore);
    gotoCatchException();
    valueIsAliveJump.link(pasm());
}

void BaselineAssembler::ret()
{
    pasm()->generateFunctionExit();
}

} // JIT namespace
} // QV4 namepsace

QT_END_NAMESPACE

#endif // QT_CONFIG(qml_jit)
