/****************************************************************************
**
** 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$
**
****************************************************************************/
#ifndef QV4JSIR_P_H
#define QV4JSIR_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include "private/qv4global_p.h"
#include <private/qqmljsmemorypool_p.h>
#include <private/qqmljsastfwd_p.h>
#include <private/qflagpointer_p.h>
#ifndef V4_BOOTSTRAP
#include <private/qqmlmetatype_p.h>
#endif

#include <QtCore/private/qnumeric_p.h>
#include <QtCore/QVector>
#include <QtCore/QString>
#include <QtCore/QBitArray>
#include <QtCore/qurl.h>
#include <QtCore/QVarLengthArray>
#include <QtCore/QDateTime>
#include <qglobal.h>

#if defined(CONST) && defined(Q_OS_WIN)
# define QT_POP_CONST
# pragma push_macro("CONST")
# undef CONST // CONST conflicts with our own identifier
#endif

QT_BEGIN_NAMESPACE

class QTextStream;
class QQmlType;
class QQmlPropertyData;
class QQmlPropertyCache;
class QQmlEnginePrivate;
struct QQmlImportRef;
class QQmlTypeNameCache;

namespace QV4 {

inline bool isNegative(double d)
{
    uchar *dch = (uchar *)&d;
    if (QSysInfo::ByteOrder == QSysInfo::BigEndian)
        return (dch[0] & 0x80);
    else
        return (dch[7] & 0x80);

}

namespace IR {

struct BasicBlock;
struct Function;
struct Module;

struct Stmt;
struct Expr;

// expressions
struct Const;
struct String;
struct RegExp;
struct Name;
struct Temp;
struct ArgLocal;
struct Closure;
struct Convert;
struct Unop;
struct Binop;
struct Call;
struct New;
struct Subscript;
struct Member;

// statements
struct Exp;
struct Move;
struct Jump;
struct CJump;
struct Ret;
struct Phi;

template<class T, int Prealloc>
class VarLengthArray: public QVarLengthArray<T, Prealloc>
{
public:
    bool removeOne(const T &element)
    {
        for (int i = 0; i < this->size(); ++i) {
            if (this->at(i) == element) {
                this->remove(i);
                return true;
            }
        }

        return false;
    }
};

// Flag pointer:
// * The first flag indicates whether the meta object is final.
//   If final, then none of its properties themselves need to
//   be final when considering for lookups in QML.
// * The second flag indicates whether enums should be included
//   in the lookup of properties or not. The default is false.
typedef QFlagPointer<QQmlPropertyCache> IRMetaObject;

enum AluOp {
    OpInvalid = 0,

    OpIfTrue,
    OpNot,
    OpUMinus,
    OpUPlus,
    OpCompl,
    OpIncrement,
    OpDecrement,

    OpBitAnd,
    OpBitOr,
    OpBitXor,

    OpAdd,
    OpSub,
    OpMul,
    OpDiv,
    OpMod,

    OpLShift,
    OpRShift,
    OpURShift,

    OpGt,
    OpLt,
    OpGe,
    OpLe,
    OpEqual,
    OpNotEqual,
    OpStrictEqual,
    OpStrictNotEqual,

    OpInstanceof,
    OpIn,

    OpAnd,
    OpOr,

    LastAluOp = OpOr
};
AluOp binaryOperator(int op);
const char *opname(IR::AluOp op);

enum Type : quint16 {
    UnknownType   = 0,

    MissingType   = 1 << 0,
    UndefinedType = 1 << 1,
    NullType      = 1 << 2,
    BoolType      = 1 << 3,

    SInt32Type    = 1 << 4,
    UInt32Type    = 1 << 5,
    DoubleType    = 1 << 6,
    NumberType    = SInt32Type | UInt32Type | DoubleType,

    StringType    = 1 << 7,
    QObjectType   = 1 << 8,
    VarType       = 1 << 9
};

inline bool strictlyEqualTypes(Type t1, Type t2)
{
    return t1 == t2 || ((t1 & NumberType) && (t2 & NumberType));
}

QString typeName(Type t);

struct MemberExpressionResolver;

struct DiscoveredType {
    int type;
    MemberExpressionResolver *memberResolver;

    DiscoveredType() : type(UnknownType), memberResolver(0) {}
    DiscoveredType(Type t) : type(t), memberResolver(0) { Q_ASSERT(type != QObjectType); }
    explicit DiscoveredType(int t) : type(t), memberResolver(0) { Q_ASSERT(type != QObjectType); }
    explicit DiscoveredType(MemberExpressionResolver *memberResolver)
        : type(QObjectType)
        , memberResolver(memberResolver)
    { Q_ASSERT(memberResolver); }

    bool test(Type t) const { return type & t; }
    bool isNumber() const { return (type & NumberType) && !(type & ~NumberType); }

    bool operator!=(Type other) const { return type != other; }
    bool operator==(Type other) const { return type == other; }
    bool operator==(const DiscoveredType &other) const { return type == other.type; }
    bool operator!=(const DiscoveredType &other) const { return type != other.type; }
};

struct MemberExpressionResolver
{
    typedef DiscoveredType (*ResolveFunction)(QQmlEnginePrivate *engine,
                                              const MemberExpressionResolver *resolver,
                                              Member *member);

    MemberExpressionResolver()
        : resolveMember(0), import(nullptr), propertyCache(nullptr), typenameCache(nullptr), owner(nullptr), flags(0) {}

    bool isValid() const { return !!resolveMember; }
    void clear() { *this = MemberExpressionResolver(); }

    ResolveFunction resolveMember;
#ifndef V4_BOOTSTRAP
    QQmlType qmlType;
#endif
    const QQmlImportRef *import;
    QQmlPropertyCache *propertyCache;
    QQmlTypeNameCache *typenameCache;
    Function *owner;
    unsigned int flags;
};

struct Q_AUTOTEST_EXPORT Expr {
    enum ExprKind : quint8 {
        NameExpr,
        TempExpr,
        ArgLocalExpr,
        SubscriptExpr,
        MemberExpr,

        LastLValue = MemberExpr,

        ConstExpr,
        StringExpr,
        RegExpExpr,
        ClosureExpr,
        ConvertExpr,
        UnopExpr,
        BinopExpr,
        CallExpr,
        NewExpr
    };

    Type type;
    const ExprKind exprKind;

    Expr &operator=(const Expr &other) {
        Q_ASSERT(exprKind == other.exprKind);
        type = other.type;
        return *this;
    }

    template <typename To>
    inline bool isa() const {
        return To::classof(this);
    }

    template <typename To>
    inline To *as() {
        if (isa<To>()) {
            return static_cast<To *>(this);
        } else {
            return nullptr;
        }
    }

    template <typename To>
    inline const To *as() const {
        if (isa<To>()) {
            return static_cast<const To *>(this);
        } else {
            return nullptr;
        }
    }

    Expr(ExprKind exprKind): type(UnknownType), exprKind(exprKind) {}
    bool isLValue() const;

    Const *asConst();
    String *asString();
    RegExp *asRegExp();
    Name *asName();
    Temp *asTemp();
    ArgLocal *asArgLocal();
    Closure *asClosure();
    Convert *asConvert();
    Unop *asUnop();
    Binop *asBinop();
    Call *asCall();
    New *asNew();
    Subscript *asSubscript();
    Member *asMember();
};

#define EXPR_VISIT_ALL_KINDS(e) \
    switch (e->exprKind) { \
    case QV4::IR::Expr::ConstExpr: \
        break; \
    case QV4::IR::Expr::StringExpr: \
        break; \
    case QV4::IR::Expr::RegExpExpr: \
        break; \
    case QV4::IR::Expr::NameExpr: \
        break; \
    case QV4::IR::Expr::TempExpr: \
        break; \
    case QV4::IR::Expr::ArgLocalExpr: \
        break; \
    case QV4::IR::Expr::ClosureExpr: \
        break; \
    case QV4::IR::Expr::ConvertExpr: { \
        auto casted = e->asConvert(); \
        visit(casted->expr); \
    } break; \
    case QV4::IR::Expr::UnopExpr: { \
        auto casted = e->asUnop(); \
        visit(casted->expr); \
    } break; \
    case QV4::IR::Expr::BinopExpr: { \
        auto casted = e->asBinop(); \
        visit(casted->left); \
        visit(casted->right); \
    } break; \
    case QV4::IR::Expr::CallExpr: { \
        auto casted = e->asCall(); \
        visit(casted->base); \
        for (QV4::IR::ExprList *it = casted->args; it; it = it->next) \
            visit(it->expr); \
    } break; \
    case QV4::IR::Expr::NewExpr: { \
        auto casted = e->asNew(); \
        visit(casted->base); \
        for (QV4::IR::ExprList *it = casted->args; it; it = it->next) \
            visit(it->expr); \
    } break; \
    case QV4::IR::Expr::SubscriptExpr: { \
        auto casted = e->asSubscript(); \
        visit(casted->base); \
        visit(casted->index); \
    } break; \
    case QV4::IR::Expr::MemberExpr: { \
        auto casted = e->asMember(); \
        visit(casted->base); \
    } break; \
    }

struct ExprList {
    Expr *expr;
    ExprList *next;

    ExprList(): expr(0), next(0) {}

    void init(Expr *expr, ExprList *next = 0)
    {
        this->expr = expr;
        this->next = next;
    }
};

struct Const: Expr {
    double value;

    Const(): Expr(ConstExpr) {}

    void init(Type type, double value)
    {
        this->type = type;
        this->value = value;
    }

    static bool classof(const Expr *c) { return c->exprKind == ConstExpr; }
};

struct String: Expr {
    const QString *value;

    String(): Expr(StringExpr) {}

    void init(const QString *value)
    {
        this->value = value;
    }

    static bool classof(const Expr *c) { return c->exprKind == StringExpr; }
};

struct RegExp: Expr {
    // needs to be compatible with the flags in the lexer, and in RegExpObject
    enum Flags {
        RegExp_Global     = 0x01,
        RegExp_IgnoreCase = 0x02,
        RegExp_Multiline  = 0x04
    };

    const QString *value;
    int flags;

    RegExp(): Expr(RegExpExpr) {}

    void init(const QString *value, int flags)
    {
        this->value = value;
        this->flags = flags;
    }

    static bool classof(const Expr *c) { return c->exprKind == RegExpExpr; }
};

struct Name: Expr {
    enum Builtin {
        builtin_invalid,
        builtin_typeof,
        builtin_delete,
        builtin_throw,
        builtin_rethrow,
        builtin_unwind_exception,
        builtin_push_catch_scope,
        builtin_foreach_iterator_object,
        builtin_foreach_next_property_name,
        builtin_push_with_scope,
        builtin_pop_scope,
        builtin_declare_vars,
        builtin_define_array,
        builtin_define_object_literal,
        builtin_setup_argument_object,
        builtin_convert_this_to_object,
        builtin_qml_context,
        builtin_qml_imported_scripts_object
    };

    const QString *id;
    Builtin builtin;
    bool global : 1;
    bool qmlSingleton : 1;
    bool freeOfSideEffects : 1;
    quint32 line;
    quint32 column;

    Name(): Expr(NameExpr) {}

    void initGlobal(const QString *id, quint32 line, quint32 column);
    void init(const QString *id, quint32 line, quint32 column);
    void init(Builtin builtin, quint32 line, quint32 column);

    static bool classof(const Expr *c) { return c->exprKind == NameExpr; }
};

struct Q_AUTOTEST_EXPORT Temp: Expr {
    enum Kind {
        Invalid = 0,
        VirtualRegister,
        PhysicalRegister,
        StackSlot
    };

    unsigned index      : 28;
    unsigned isReadOnly :  1;
    unsigned kind       :  3;

    // Used when temp is used as base in member expression
    MemberExpressionResolver *memberResolver;

    Temp()
        : Expr(TempExpr)
        , index((1 << 28) - 1)
        , isReadOnly(0)
        , kind(Invalid)
        , memberResolver(0)
    {}

    Temp(Type type, Kind kind, unsigned index)
        : Expr(TempExpr)
        , index(index)
        , isReadOnly(0)
        , kind(kind)
        , memberResolver(0)
    {
        this->type = type;
    }

    void init(unsigned kind, unsigned index)
    {
        this->index = index;
        this->isReadOnly = false;
        this->kind = kind;
    }

    bool isInvalid() const { return kind == Invalid; }

    static bool classof(const Expr *c) { return c->exprKind == TempExpr; }
};

inline bool operator==(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW
{ return t1.index == t2.index && t1.kind == t2.kind && t1.type == t2.type; }

inline bool operator!=(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW
{ return !(t1 == t2); }

inline uint qHash(const Temp &t, uint seed = 0) Q_DECL_NOTHROW
{ return t.index ^ t.kind ^ seed; }

bool operator<(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW;

struct Q_AUTOTEST_EXPORT ArgLocal: Expr {
    enum Kind {
        Formal = 0,
        ScopedFormal,
        Local,
        ScopedLocal
    };

    unsigned index;
    unsigned scope             : 29; // how many scopes outside the current one?
    unsigned kind              :  2;
    unsigned isArgumentsOrEval :  1;

    void init(unsigned kind, unsigned index, unsigned scope)
    {
        Q_ASSERT((kind == ScopedLocal && scope != 0) ||
                 (kind == ScopedFormal && scope != 0) ||
                 (scope == 0));

        this->kind = kind;
        this->index = index;
        this->scope = scope;
        this->isArgumentsOrEval = false;
    }

    ArgLocal(): Expr(ArgLocalExpr) {}

    bool operator==(const ArgLocal &other) const
    { return index == other.index && scope == other.scope && kind == other.kind; }

    static bool classof(const Expr *c) { return c->exprKind == ArgLocalExpr; }
};

struct Closure: Expr {
    int value; // index in _module->functions
    const QString *functionName;

    Closure(): Expr(ClosureExpr) {}

    void init(int functionInModule, const QString *functionName)
    {
        this->value = functionInModule;
        this->functionName = functionName;
    }

    static bool classof(const Expr *c) { return c->exprKind == ClosureExpr; }
};

struct Convert: Expr {
    Expr *expr;

    Convert(): Expr(ConvertExpr) {}

    void init(Expr *expr, Type type)
    {
        this->expr = expr;
        this->type = type;
    }

    static bool classof(const Expr *c) { return c->exprKind == ConvertExpr; }
};

struct Unop: Expr {
    Expr *expr;
    AluOp op;

    Unop(): Expr(UnopExpr) {}

    void init(AluOp op, Expr *expr)
    {
        this->op = op;
        this->expr = expr;
    }

    static bool classof(const Expr *c) { return c->exprKind == UnopExpr; }
};

struct Binop: Expr {
    Expr *left; // Temp or Const
    Expr *right; // Temp or Const
    AluOp op;

    Binop(): Expr(BinopExpr) {}

    void init(AluOp op, Expr *left, Expr *right)
    {
        this->op = op;
        this->left = left;
        this->right = right;
    }

    static bool classof(const Expr *c) { return c->exprKind == BinopExpr; }
};

struct Call: Expr {
    Expr *base; // Name, Member, Temp
    ExprList *args; // List of Temps

    Call(): Expr(CallExpr) {}

    void init(Expr *base, ExprList *args)
    {
        this->base = base;
        this->args = args;
    }

    Expr *onlyArgument() const {
        if (args && ! args->next)
            return args->expr;
        return 0;
    }

    static bool classof(const Expr *c) { return c->exprKind == CallExpr; }
};

struct New: Expr {
    Expr *base; // Name, Member, Temp
    ExprList *args; // List of Temps

    New(): Expr(NewExpr) {}

    void init(Expr *base, ExprList *args)
    {
        this->base = base;
        this->args = args;
    }

    Expr *onlyArgument() const {
        if (args && ! args->next)
            return args->expr;
        return 0;
    }

    static bool classof(const Expr *c) { return c->exprKind == NewExpr; }
};

struct Subscript: Expr {
    Expr *base;
    Expr *index;

    Subscript(): Expr(SubscriptExpr) {}

    void init(Expr *base, Expr *index)
    {
        this->base = base;
        this->index = index;
    }

    static bool classof(const Expr *c) { return c->exprKind == SubscriptExpr; }
};

struct Member: Expr {
    // Used for property dependency tracking
    enum MemberKind {
        UnspecifiedMember,
        MemberOfEnum,
        MemberOfQmlScopeObject,
        MemberOfQmlContextObject,
        MemberOfIdObjectsArray,
        MemberOfSingletonObject,
    };

    Expr *base;
    const QString *name;
    QQmlPropertyData *property;
    union {  // depending on kind
        int attachedPropertiesId;
        int enumValue;
        int idIndex;
    };
    uchar freeOfSideEffects : 1;

    // This is set for example for for QObject properties. All sorts of extra behavior
    // is defined when writing to them, for example resettable properties are reset
    // when writing undefined to them, and an exception is thrown when they're missing
    // a reset function. And then there's also Qt.binding().
    uchar inhibitTypeConversionOnWrite: 1;

    uchar kind: 3; // MemberKind

    Member(): Expr(MemberExpr) {}

    void setEnumValue(int value) {
        kind = MemberOfEnum;
        enumValue = value;
    }

    void setAttachedPropertiesId(int id) {
        Q_ASSERT(kind != MemberOfEnum && kind != MemberOfIdObjectsArray);
        attachedPropertiesId = id;
    }

    void init(Expr *base, const QString *name, QQmlPropertyData *property = 0, uchar kind = UnspecifiedMember, int index = 0)
    {
        this->base = base;
        this->name = name;
        this->property = property;
        this->idIndex = index;
        this->freeOfSideEffects = false;
        this->inhibitTypeConversionOnWrite = property != 0;
        this->kind = kind;
    }

    static bool classof(const Expr *c) { return c->exprKind == MemberExpr; }
};

inline bool Expr::isLValue() const {
    if (auto t = as<Temp>())
        return !t->isReadOnly;
    return exprKind <= LastLValue;
}

struct Stmt {
    enum StmtKind: quint8 {
        MoveStmt,
        ExpStmt,
        JumpStmt,
        CJumpStmt,
        RetStmt,
        PhiStmt
    };

    template <typename To>
    inline bool isa() const {
        return To::classof(this);
    }

    template <typename To>
    inline To *as() {
        if (isa<To>())
            return static_cast<To *>(this);
        else
            return nullptr;
    }

    enum { InvalidId = -1 };

    QQmlJS::AST::SourceLocation location;

    explicit Stmt(int id, StmtKind stmtKind): _id(id), stmtKind(stmtKind) {}

    Stmt *asTerminator();

    Exp *asExp();
    Move *asMove();
    Jump *asJump();
    CJump *asCJump();
    Ret *asRet();
    Phi *asPhi();

    int id() const { return _id; }

private: // For memory management in BasicBlock
    friend struct BasicBlock;

private:
    friend struct Function;
    int _id;

public:
    const StmtKind stmtKind;
};

#define STMT_VISIT_ALL_KINDS(s) \
    switch (s->stmtKind) { \
    case QV4::IR::Stmt::MoveStmt: { \
        auto casted = s->asMove(); \
        visit(casted->target); \
        visit(casted->source); \
    } break; \
    case QV4::IR::Stmt::ExpStmt: { \
        auto casted = s->asExp(); \
        visit(casted->expr); \
    } break; \
    case QV4::IR::Stmt::JumpStmt: \
        break; \
    case QV4::IR::Stmt::CJumpStmt: { \
        auto casted = s->asCJump(); \
        visit(casted->cond); \
    } break; \
    case QV4::IR::Stmt::RetStmt: { \
        auto casted = s->asRet(); \
        visit(casted->expr); \
    } break; \
    case QV4::IR::Stmt::PhiStmt: { \
        auto casted = s->asPhi(); \
        visit(casted->targetTemp); \
        for (auto *e : casted->incoming) { \
            visit(e); \
        } \
    } break; \
    }

struct Exp: Stmt {
    Expr *expr;

    Exp(int id): Stmt(id, ExpStmt) {}

    void init(Expr *expr)
    {
        this->expr = expr;
    }

    static bool classof(const Stmt *c) { return c->stmtKind == ExpStmt; }
};

struct Move: Stmt {
    Expr *target; // LHS - Temp, Name, Member or Subscript
    Expr *source;
    bool swap;

    Move(int id): Stmt(id, MoveStmt) {}

    void init(Expr *target, Expr *source)
    {
        this->target = target;
        this->source = source;
        this->swap = false;
    }

    static bool classof(const Stmt *c) { return c->stmtKind == MoveStmt; }
};

struct Jump: Stmt {
    BasicBlock *target;

    Jump(int id): Stmt(id, JumpStmt) {}

    void init(BasicBlock *target)
    {
        this->target = target;
    }

    static bool classof(const Stmt *c) { return c->stmtKind == JumpStmt; }
};

struct CJump: Stmt {
    Expr *cond; // Temp, Binop
    BasicBlock *iftrue;
    BasicBlock *iffalse;
    BasicBlock *parent;

    CJump(int id): Stmt(id, CJumpStmt) {}

    void init(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse, BasicBlock *parent)
    {
        this->cond = cond;
        this->iftrue = iftrue;
        this->iffalse = iffalse;
        this->parent = parent;
    }

    static bool classof(const Stmt *c) { return c->stmtKind == CJumpStmt; }
};

struct Ret: Stmt {
    Expr *expr;

    Ret(int id): Stmt(id, RetStmt) {}

    void init(Expr *expr)
    {
        this->expr = expr;
    }

    static bool classof(const Stmt *c) { return c->stmtKind == RetStmt; }
};

// Phi nodes can only occur at the start of a basic block. If there are any, they need to be
// subsequent to eachother, and the first phi node should be the first statement in the basic-block.
// A number of loops rely on this behavior, so they don't need to walk through the whole list
// of instructions in a basic-block (e.g. the calls to destroyData in BasicBlock::~BasicBlock).
struct Phi: Stmt {
    Temp *targetTemp;
    VarLengthArray<Expr *, 4> incoming;

    Phi(int id): Stmt(id, PhiStmt) {}

    static bool classof(const Stmt *c) { return c->stmtKind == PhiStmt; }

    void destroyData()
    { incoming.~VarLengthArray(); }
};

inline Stmt *Stmt::asTerminator()
{
    if (auto s = asJump()) {
        return s;
    } else if (auto s = asCJump()) {
        return s;
    } else if (auto s = asRet()) {
        return s;
    } else {
        return nullptr;
    }
}

struct Q_QML_PRIVATE_EXPORT Module {
    QQmlJS::MemoryPool pool;
    QVector<Function *> functions;
    Function *rootFunction;
    QString fileName;
    QDateTime sourceTimeStamp;
    bool isQmlModule; // implies rootFunction is always 0
    uint unitFlags; // flags merged into CompiledData::Unit::flags
    QString targetABI; // fallback to QSysInfo::buildAbi() if empty
#ifdef QT_NO_QML_DEBUGGER
    static const bool debugMode = false;
#else
    bool debugMode;
#endif

    Function *newFunction(const QString &name, Function *outer);

    Module(bool debugMode)
        : rootFunction(0)
        , isQmlModule(false)
        , unitFlags(0)
#ifndef QT_NO_QML_DEBUGGER
        , debugMode(debugMode)
    {}
#else
    { Q_UNUSED(debugMode); }
#endif
    ~Module();

    void setFileName(const QString &name);
};

struct BasicBlock {
private:
    Q_DISABLE_COPY(BasicBlock)

public:
    typedef VarLengthArray<BasicBlock *, 4> IncomingEdges;
    typedef VarLengthArray<BasicBlock *, 2> OutgoingEdges;

    Function *function;
    BasicBlock *catchBlock;
    IncomingEdges in;
    OutgoingEdges out;
    QQmlJS::AST::SourceLocation nextLocation;

    BasicBlock(Function *function, BasicBlock *catcher)
        : function(function)
        , catchBlock(catcher)
        , _containingGroup(0)
        , _index(-1)
        , _isExceptionHandler(false)
        , _groupStart(false)
        , _isRemoved(false)
    {}

    ~BasicBlock()
    {
        for (Stmt *s : qAsConst(_statements)) {
            if (Phi *p = s->asPhi()) {
                p->destroyData();
            } else {
                break;
            }
        }
    }

    const QVector<Stmt *> &statements() const
    {
        Q_ASSERT(!isRemoved());
        return _statements;
    }

    int statementCount() const
    {
        Q_ASSERT(!isRemoved());
        return _statements.size();
    }

    void setStatements(const QVector<Stmt *> &newStatements);

    template <typename Instr> inline Instr i(Instr i)
    {
        Q_ASSERT(!isRemoved());
        appendStatement(i);
        return i;
    }

    void appendStatement(Stmt *statement)
    {
        Q_ASSERT(!isRemoved());
        if (nextLocation.startLine)
            statement->location = nextLocation;
        _statements.append(statement);
    }

    void prependStatement(Stmt *stmt)
    {
        Q_ASSERT(!isRemoved());
        _statements.prepend(stmt);
    }

    void prependStatements(const QVector<Stmt *> &stmts)
    {
        Q_ASSERT(!isRemoved());
        QVector<Stmt *> newStmts = stmts;
        newStmts += _statements;
        _statements = newStmts;
    }

    void insertStatementBefore(Stmt *before, Stmt *newStmt)
    {
        int idx = _statements.indexOf(before);
        Q_ASSERT(idx >= 0);
        _statements.insert(idx, newStmt);
    }

    void insertStatementBefore(int index, Stmt *newStmt)
    {
        Q_ASSERT(index >= 0);
        _statements.insert(index, newStmt);
    }

    void insertStatementBeforeTerminator(Stmt *stmt)
    {
        Q_ASSERT(!isRemoved());
        _statements.insert(_statements.size() - 1, stmt);
    }

    void replaceStatement(int index, Stmt *newStmt)
    {
        Q_ASSERT(!isRemoved());
        if (Phi *p = _statements[index]->asPhi()) {
            p->destroyData();
        }
        _statements[index] = newStmt;
    }

    void removeStatement(Stmt *stmt)
    {
        Q_ASSERT(!isRemoved());
        if (Phi *p = stmt->asPhi()) {
            p->destroyData();
        }
        _statements.remove(_statements.indexOf(stmt));
    }

    void removeStatement(int idx)
    {
        Q_ASSERT(!isRemoved());
        if (Phi *p = _statements[idx]->asPhi()) {
            p->destroyData();
        }
        _statements.remove(idx);
    }

    inline bool isEmpty() const {
        Q_ASSERT(!isRemoved());
        return _statements.isEmpty();
    }

    inline Stmt *terminator() const {
        Q_ASSERT(!isRemoved());
        if (! _statements.isEmpty() && _statements.last()->asTerminator() != 0)
            return _statements.last();
        return 0;
    }

    inline bool isTerminated() const {
        Q_ASSERT(!isRemoved());
        if (terminator() != 0)
            return true;
        return false;
    }

    enum TempForWhom {
        NewTempForCodegen,
        NewTempForOptimizer
    };
    unsigned newTemp(TempForWhom tfw = NewTempForCodegen);

    Temp *TEMP(unsigned kind);
    ArgLocal *ARG(unsigned index, unsigned scope);
    ArgLocal *LOCAL(unsigned index, unsigned scope);

    Expr *CONST(Type type, double value);
    Expr *STRING(const QString *value);
    Expr *REGEXP(const QString *value, int flags);

    Name *NAME(const QString &id, quint32 line, quint32 column);
    Name *NAME(Name::Builtin builtin, quint32 line, quint32 column);

    Name *GLOBALNAME(const QString &id, quint32 line, quint32 column);

    Closure *CLOSURE(int functionInModule);

    Expr *CONVERT(Expr *expr, Type type);
    Expr *UNOP(AluOp op, Expr *expr);
    Expr *BINOP(AluOp op, Expr *left, Expr *right);
    Expr *CALL(Expr *base, ExprList *args = 0);
    Expr *NEW(Expr *base, ExprList *args = 0);
    Expr *SUBSCRIPT(Expr *base, Expr *index);
    Expr *MEMBER(Expr *base, const QString *name, QQmlPropertyData *property = 0, uchar kind = Member::UnspecifiedMember, int attachedPropertiesIdOrEnumValue = 0);

    Stmt *EXP(Expr *expr);

    Stmt *MOVE(Expr *target, Expr *source);

    Stmt *JUMP(BasicBlock *target);
    Stmt *CJUMP(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse);
    Stmt *RET(Expr *expr);

    BasicBlock *containingGroup() const
    {
        Q_ASSERT(!isRemoved());
        return _containingGroup;
    }

    void setContainingGroup(BasicBlock *loopHeader)
    {
        Q_ASSERT(!isRemoved());
        _containingGroup = loopHeader;
    }

    bool isGroupStart() const
    {
        Q_ASSERT(!isRemoved());
        return _groupStart;
    }

    void markAsGroupStart(bool mark = true)
    {
        Q_ASSERT(!isRemoved());
        _groupStart = mark;
    }

    // Returns the index of the basic-block.
    // See Function for the full description.
    int index() const
    {
        Q_ASSERT(!isRemoved());
        return _index;
    }

    bool isExceptionHandler() const
    { return _isExceptionHandler; }

    void setExceptionHandler(bool onoff)
    { _isExceptionHandler = onoff; }

    bool isRemoved() const
    { return _isRemoved; }

private: // For Function's eyes only.
    friend struct Function;
    void setIndex(int index)
    {
        Q_ASSERT(_index < 0);
        changeIndex(index);
    }

    void changeIndex(int index)
    {
        Q_ASSERT(index >= 0);
        _index = index;
    }

    void markAsRemoved()
    {
        _isRemoved = true;
        _index = -1;
    }

private:
    QVector<Stmt *> _statements;
    BasicBlock *_containingGroup;
    int _index;
    unsigned _isExceptionHandler : 1;
    unsigned _groupStart : 1;
    unsigned _isRemoved : 1;
};

template <typename T>
class SmallSet: public QVarLengthArray<T, 8>
{
public:
    void insert(int value)
    {
        for (auto it : *this) {
            if (it == value)
                return;
        }
        this->append(value);
    }
};

// Map from meta property index (existence implies dependency) to notify signal index
struct KeyValuePair
{
    quint32 _key;
    quint32 _value;

    KeyValuePair(): _key(0), _value(0) {}
    KeyValuePair(quint32 key, quint32 value): _key(key), _value(value) {}

    quint32 key() const { return _key; }
    quint32 value() const { return _value; }
};

class PropertyDependencyMap: public QVarLengthArray<KeyValuePair, 8>
{
public:
    void insert(quint32 key, quint32 value)
    {
        for (auto it = begin(), eit = end(); it != eit; ++it) {
            if (it->_key == key) {
                it->_value = value;
                return;
            }
        }
        append(KeyValuePair(key, value));
    }
};

// The Function owns (manages), among things, a list of basic-blocks. All the blocks have an index,
// which corresponds to the index in the entry/index in the vector in which they are stored. This
// means that algorithms/classes can also store any information about a basic block in an array,
// where the index corresponds to the index of the basic block, which can then be used to query
// the function for a pointer to a basic block. This also means that basic-blocks cannot be removed
// or renumbered.
//
// Note that currently there is one exception: after optimization and block scheduling, the
// method setScheduledBlocks can be called once, to register a newly ordered list. For debugging
// purposes, these blocks are not immediately renumbered, so renumberBasicBlocks should be called
// immediately after changing the order. That will restore the property of having a corresponding
// block-index and block-position-in-basicBlocks-vector.
//
// In order for optimization/transformation passes to skip uninteresting basic blocks that will be
// removed, the block can be marked as such. After doing so, any access will result in a failing
// assertion.
struct Function {
    Module *module;
    QQmlJS::MemoryPool *pool;
    const QString *name;
    int currentTemp = 0;
    int tempCount;
    int maxNumberOfArguments;
    QSet<QString> strings;
    QList<const QString *> formals;
    QList<const QString *> locals;
    QVector<Function *> nestedFunctions;
    Function *outer;

    int insideWithOrCatch;

    uint hasDirectEval: 1;
    uint usesArgumentsObject : 1;
    uint usesThis : 1;
    uint isStrict: 1;
    uint isNamedExpression : 1;
    uint hasTry: 1;
    uint hasWith: 1;
    uint isQmlBinding: 1;
    uint unused : 24;

    // Location of declaration in source code (0 if not specified)
    uint line;
    uint column;

    // Qml extension:
    SmallSet<int> idObjectDependencies;
    PropertyDependencyMap contextObjectPropertyDependencies;
    PropertyDependencyMap scopeObjectPropertyDependencies;

    template <typename T> T *New() { return new (pool->allocate(sizeof(T))) T(); }
    template <typename T> T *NewStmt() {
        return new (pool->allocate(sizeof(T))) T(getNewStatementId());
    }

    Function(Module *module, Function *outer, const QString &name);
    ~Function();

    enum BasicBlockInsertMode {
        InsertBlock,
        DontInsertBlock
    };

    BasicBlock *newBasicBlock(BasicBlock *catchBlock, BasicBlockInsertMode mode = InsertBlock);
    const QString *newString(const QString &text);

    void RECEIVE(const QString &name) { formals.append(newString(name)); }
    void LOCAL(const QString &name) { locals.append(newString(name)); }

    BasicBlock *addBasicBlock(BasicBlock *block);
    void removeBasicBlock(BasicBlock *block);

    const QVector<BasicBlock *> &basicBlocks() const
    { return _basicBlocks; }

    BasicBlock *basicBlock(int idx) const
    { return _basicBlocks.at(idx); }

    int basicBlockCount() const
    { return _basicBlocks.size(); }

    int liveBasicBlocksCount() const;

    void removeSharedExpressions();

    int indexOfArgument(const QStringRef &string) const;

    bool variablesCanEscape() const
    { return hasDirectEval || !nestedFunctions.isEmpty() || module->debugMode; }

    void setScheduledBlocks(const QVector<BasicBlock *> &scheduled);

    int getNewStatementId() { return _statementCount++; }
    int statementCount() const { return _statementCount; }

    bool canUseSimpleCall() const {
        return nestedFunctions.isEmpty() &&
               locals.isEmpty() && formals.size() <= QV4::Global::ReservedArgumentCount &&
               !hasTry && !hasWith && !isNamedExpression && !usesArgumentsObject && !hasDirectEval;
    }

    bool argLocalRequiresWriteBarrier(ArgLocal *al) const {
        uint scope = al->scope;
        const IR::Function *f = this;
        while (scope) {
            f = f->outer;
            --scope;
        }
        return !f->canUseSimpleCall();
    }
    int localsCountForScope(ArgLocal *al) const {
        uint scope = al->scope;
        const IR::Function *f = this;
        while (scope) {
            f = f->outer;
            --scope;
        }
        return f->locals.size();
    }

private:
    BasicBlock *getOrCreateBasicBlock(int index);
    void setStatementCount(int cnt);

private:
    QVector<BasicBlock *> _basicBlocks;
    QVector<BasicBlock *> *_allBasicBlocks;
    int _statementCount;
};

class CloneExpr
{
public:
    explicit CloneExpr(IR::BasicBlock *block = 0);

    void setBasicBlock(IR::BasicBlock *block);

    template <typename ExprSubclass>
    ExprSubclass *operator()(ExprSubclass *expr)
    {
        return clone(expr);
    }

    template <typename ExprSubclass>
    ExprSubclass *clone(ExprSubclass *expr)
    {
        Expr *c = expr;
        qSwap(cloned, c);
        visit(expr);
        qSwap(cloned, c);
        return static_cast<ExprSubclass *>(c);
    }

    static Const *cloneConst(Const *c, Function *f)
    {
        Const *newConst = f->New<Const>();
        newConst->init(c->type, c->value);
        return newConst;
    }

    static Name *cloneName(Name *n, Function *f)
    {
        Name *newName = f->New<Name>();
        newName->type = n->type;
        newName->id = n->id;
        newName->builtin = n->builtin;
        newName->global = n->global;
        newName->qmlSingleton = n->qmlSingleton;
        newName->freeOfSideEffects = n->freeOfSideEffects;
        newName->line = n->line;
        newName->column = n->column;
        return newName;
    }

    static Temp *cloneTemp(Temp *t, Function *f)
    {
        Temp *newTemp = f->New<Temp>();
        newTemp->init(t->kind, t->index);
        newTemp->type = t->type;
        newTemp->memberResolver = t->memberResolver;
        return newTemp;
    }

    static ArgLocal *cloneArgLocal(ArgLocal *argLocal, Function *f)
    {
        ArgLocal *newArgLocal = f->New<ArgLocal>();
        newArgLocal->init(argLocal->kind, argLocal->index, argLocal->scope);
        newArgLocal->type = argLocal->type;
        newArgLocal->isArgumentsOrEval = argLocal->isArgumentsOrEval;
        return newArgLocal;
    }

private:
    IR::ExprList *clone(IR::ExprList *list);

    void visit(Expr *e);

protected:
    IR::BasicBlock *block;

private:
    IR::Expr *cloned;
};

class Q_AUTOTEST_EXPORT IRPrinter
{
public:
    IRPrinter(QTextStream *out);
    virtual ~IRPrinter();

    void print(Stmt *s);
    void print(Expr *e);
    void print(const Expr &e);

    virtual void print(Function *f);
    virtual void print(BasicBlock *bb);

    void visit(Stmt *s);
    virtual void visitExp(Exp *s);
    virtual void visitMove(Move *s);
    virtual void visitJump(Jump *s);
    virtual void visitCJump(CJump *s);
    virtual void visitRet(Ret *s);
    virtual void visitPhi(Phi *s);

    void visit(Expr *e);
    virtual void visitConst(Const *e);
    virtual void visitString(String *e);
    virtual void visitRegExp(RegExp *e);
    virtual void visitName(Name *e);
    virtual void visitTemp(Temp *e);
    virtual void visitArgLocal(ArgLocal *e);
    virtual void visitClosure(Closure *e);
    virtual void visitConvert(Convert *e);
    virtual void visitUnop(Unop *e);
    virtual void visitBinop(Binop *e);
    virtual void visitCall(Call *e);
    virtual void visitNew(New *e);
    virtual void visitSubscript(Subscript *e);
    virtual void visitMember(Member *e);

    static QString escape(const QString &s);

protected:
    virtual void addStmtNr(Stmt *s);
    void addJustifiedNr(int pos);
    void printBlockStart();

protected:
    QTextStream *out;
    int positionSize;
    BasicBlock *currentBB;
};

inline unsigned BasicBlock::newTemp(TempForWhom tfw)
{
    Q_ASSERT(!isRemoved());

    if (tfw == NewTempForOptimizer)
        return function->tempCount++;

    int t = function->currentTemp++;
    if (function->tempCount < function->currentTemp)
        function->tempCount = function->currentTemp;
    return t;
}

inline Temp *BasicBlock::TEMP(unsigned index)
{
    Q_ASSERT(!isRemoved());
    Temp *e = function->New<Temp>();
    e->init(Temp::VirtualRegister, index);
    return e;
}

inline ArgLocal *BasicBlock::ARG(unsigned index, unsigned scope)
{
    Q_ASSERT(!isRemoved());
    ArgLocal *e = function->New<ArgLocal>();
    e->init(scope ? ArgLocal::ScopedFormal : ArgLocal::Formal, index, scope);
    return e;
}

inline ArgLocal *BasicBlock::LOCAL(unsigned index, unsigned scope)
{
    Q_ASSERT(!isRemoved());
    ArgLocal *e = function->New<ArgLocal>();
    e->init(scope ? ArgLocal::ScopedLocal : ArgLocal::Local, index, scope);
    return e;
}

inline Expr *BasicBlock::CONST(Type type, double value)
{
    Q_ASSERT(!isRemoved());
    Const *e = function->New<Const>();
    if (type == NumberType) {
        int ival = (int)value;
        // +0 != -0, so we need to convert to double when negating 0
        if (ival == value && !(value == 0 && isNegative(value)))
            type = SInt32Type;
        else
            type = DoubleType;
    } else if (type == NullType) {
        value = 0;
    } else if (type == UndefinedType) {
        value = qt_qnan();
    }

    e->init(type, value);
    return e;
}

inline Expr *BasicBlock::STRING(const QString *value)
{
    Q_ASSERT(!isRemoved());
    String *e = function->New<String>();
    e->init(value);
    return e;
}

inline Expr *BasicBlock::REGEXP(const QString *value, int flags)
{
    Q_ASSERT(!isRemoved());
    RegExp *e = function->New<RegExp>();
    e->init(value, flags);
    return e;
}

inline Name *BasicBlock::NAME(const QString &id, quint32 line, quint32 column)
{
    Q_ASSERT(!isRemoved());
    Name *e = function->New<Name>();
    e->init(function->newString(id), line, column);
    return e;
}

inline Name *BasicBlock::GLOBALNAME(const QString &id, quint32 line, quint32 column)
{
    Q_ASSERT(!isRemoved());
    Name *e = function->New<Name>();
    e->initGlobal(function->newString(id), line, column);
    return e;
}


inline Name *BasicBlock::NAME(Name::Builtin builtin, quint32 line, quint32 column)
{
    Q_ASSERT(!isRemoved());
    Name *e = function->New<Name>();
    e->init(builtin, line, column);
    return e;
}

inline Closure *BasicBlock::CLOSURE(int functionInModule)
{
    Q_ASSERT(!isRemoved());
    Closure *clos = function->New<Closure>();
    clos->init(functionInModule, function->module->functions.at(functionInModule)->name);
    return clos;
}

inline Expr *BasicBlock::CONVERT(Expr *expr, Type type)
{
    Q_ASSERT(!isRemoved());
    Convert *e = function->New<Convert>();
    e->init(expr, type);
    return e;
}

inline Expr *BasicBlock::UNOP(AluOp op, Expr *expr)
{
    Q_ASSERT(!isRemoved());
    Unop *e = function->New<Unop>();
    e->init(op, expr);
    return e;
}

inline Expr *BasicBlock::BINOP(AluOp op, Expr *left, Expr *right)
{
    Q_ASSERT(!isRemoved());
    Binop *e = function->New<Binop>();
    e->init(op, left, right);
    return e;
}

inline Expr *BasicBlock::CALL(Expr *base, ExprList *args)
{
    Q_ASSERT(!isRemoved());
    Call *e = function->New<Call>();
    e->init(base, args);
    int argc = 0;
    for (ExprList *it = args; it; it = it->next)
        ++argc;
    function->maxNumberOfArguments = qMax(function->maxNumberOfArguments, argc);
    return e;
}

inline Expr *BasicBlock::NEW(Expr *base, ExprList *args)
{
    Q_ASSERT(!isRemoved());
    New *e = function->New<New>();
    e->init(base, args);
    return e;
}

inline Expr *BasicBlock::SUBSCRIPT(Expr *base, Expr *index)
{
    Q_ASSERT(!isRemoved());
    Subscript *e = function->New<Subscript>();
    e->init(base, index);
    return e;
}

inline Expr *BasicBlock::MEMBER(Expr *base, const QString *name, QQmlPropertyData *property, uchar kind, int attachedPropertiesIdOrEnumValue)
{
    Q_ASSERT(!isRemoved());
    Member*e = function->New<Member>();
    e->init(base, name, property, kind, attachedPropertiesIdOrEnumValue);
    return e;
}

inline Stmt *BasicBlock::EXP(Expr *expr)
{
    Q_ASSERT(!isRemoved());
    if (isTerminated())
        return 0;

    Exp *s = function->NewStmt<Exp>();
    s->init(expr);
    appendStatement(s);
    return s;
}

inline Stmt *BasicBlock::MOVE(Expr *target, Expr *source)
{
    Q_ASSERT(!isRemoved());
    if (isTerminated())
        return 0;

    Move *s = function->NewStmt<Move>();
    s->init(target, source);
    appendStatement(s);
    return s;
}

inline Stmt *BasicBlock::JUMP(BasicBlock *target)
{
    Q_ASSERT(!isRemoved());
    if (isTerminated())
        return 0;

    Jump *s = function->NewStmt<Jump>();
    s->init(target);
    appendStatement(s);

    Q_ASSERT(! out.contains(target));
    out.append(target);

    Q_ASSERT(! target->in.contains(this));
    target->in.append(this);

    return s;
}

inline Stmt *BasicBlock::CJUMP(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse)
{
    Q_ASSERT(!isRemoved());
    if (isTerminated())
        return 0;

    if (iftrue == iffalse) {
        MOVE(TEMP(newTemp()), cond);
        return JUMP(iftrue);
    }

    CJump *s = function->NewStmt<CJump>();
    s->init(cond, iftrue, iffalse, this);
    appendStatement(s);

    Q_ASSERT(! out.contains(iftrue));
    out.append(iftrue);

    Q_ASSERT(! iftrue->in.contains(this));
    iftrue->in.append(this);

    Q_ASSERT(! out.contains(iffalse));
    out.append(iffalse);

    Q_ASSERT(! iffalse->in.contains(this));
    iffalse->in.append(this);

    return s;
}

inline Stmt *BasicBlock::RET(Expr *expr)
{
    Q_ASSERT(!isRemoved());
    if (isTerminated())
        return 0;

    Ret *s = function->NewStmt<Ret>();
    s->init(expr);
    appendStatement(s);
    return s;
}

inline Const *Expr::asConst() { return as<Const>(); }
inline String *Expr::asString() { return as<String>(); }
inline RegExp *Expr::asRegExp() { return as<RegExp>(); }
inline Name *Expr::asName() { return as<Name>(); }
inline Temp *Expr::asTemp() { return as<Temp>(); }
inline ArgLocal *Expr::asArgLocal() { return as<ArgLocal>(); }
inline Closure *Expr::asClosure() { return as<Closure>(); }
inline Convert *Expr::asConvert() { return as<Convert>(); }
inline Unop *Expr::asUnop() { return as<Unop>(); }
inline Binop *Expr::asBinop() { return as<Binop>(); }
inline Call *Expr::asCall() { return as<Call>(); }
inline New *Expr::asNew() { return as<New>(); }
inline Subscript *Expr::asSubscript() { return as<Subscript>(); }
inline Member *Expr::asMember() { return as<Member>(); }

inline Exp *Stmt::asExp() { return as<Exp>(); }
inline Move *Stmt::asMove() { return as<Move>(); }
inline Jump *Stmt::asJump() { return as<Jump>(); }
inline CJump *Stmt::asCJump() { return as<CJump>(); }
inline Ret *Stmt::asRet() { return as<Ret>(); }
inline Phi *Stmt::asPhi() { return as<Phi>(); }

} // end of namespace IR

} // end of namespace QV4

QT_END_NAMESPACE

#if defined(QT_POP_CONST)
# pragma pop_macro("CONST") // Restore peace
# undef QT_POP_CONST
#endif

#endif // QV4IR_P_H
