/*===================== begin_copyright_notice ==================================

Copyright (c) 2017 Intel Corporation

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


======================= end_copyright_notice ==================================*/

#include "IRChecker.hpp"
#include "../api/iga.h"

#include <sstream>

using namespace iga;


struct Checker {
    ErrorHandler *m_errHandler;
    const Model  &m_model;

    Checker(const Model &model, ErrorHandler *err) : m_errHandler(err), m_model(model) { }

    void warning(const Loc &loc, const char *msg) {
        m_errHandler->reportWarning(loc, msg);
    }
    void error(const Loc &loc, const char *msg) {
        m_errHandler->reportError(loc, msg);
    }
};

struct LOCChecker : Checker {
    Loc m_loc;

    LOCChecker(const Model &model, Loc loc, ErrorHandler *err)
        : Checker(model, err), m_loc(loc)
    { }

    void warning(const char *msg) {
        m_errHandler->reportWarning(m_loc, msg);
    }
    void error(const char *msg) {
        m_errHandler->reportError(m_loc, msg);
    }
};


#if 0
// TODO: remove if not used
struct RegisterRegioningChecker : Checker {
    int execSize;

    bool dstIsDirect;
    int dstTypeSz;
    int dstSubReg;
    int dstRgnH;

    int srcReg, srcSubReg;
    int srcRgnV, srcRgnW, srcRgnH;
    int srcTypeSz;

    RegisterRegioningChecker(
        ErrorHandler *err,
        const Instruction &inst,
        const Operand &dst,
        const Operand &src)
        : Checker(err)
    {
    }
    void checkAll() {
    }
};
#endif

struct SemanticChecker : LOCChecker {
    const Instruction *m_inst;
    uint32_t           m_enabled_warnings;

    SemanticChecker(
        const Model &model,
        ErrorHandler *err,
        uint32_t enabled_warnings)
        : LOCChecker(model, Loc::INVALID, err)
        , m_inst(nullptr)
        , m_enabled_warnings(enabled_warnings)
    { }

    void checkKernel(const Kernel &k) {
        for (auto b : k.getBlockList()) {
            for (auto i : b->getInstList()) {
                checkInstruction(*i);
            }
        }
    }

    void checkInstruction(const Instruction &i) {
        m_inst = &i;
        m_loc = i.getLoc();
        checkInstImpl(i);
        m_inst = nullptr;
        m_loc = Loc::INVALID;
    }

    void checkInstImpl(const Instruction &i) {
        if (i.getOpSpec().supportsDestination()) {
            checkDst(i, i.getDestination());
        }
        int srcs = i.getSourceCount();
        if (srcs > 0) {
            checkSrc(i, 0);
        }
        if (srcs > 1) {
            checkSrc(i, 1);
        }
        if (srcs > 2) {
            checkSrc(i, 2);
        }
    }

    void checkDst(const Instruction &i, const Operand &op) {
        switch (op.getKind()) {
        case Operand::Kind::DIRECT:
            // if (op.getDirRegName() == RegName::ARF_CE) {
            //    warning("register is not writable (except in SIP)");
            // }
            if ((m_enabled_warnings & IGA_WARNINGS_SCHED) &&
                !i.hasInstOpt(InstOpt::SWITCH) &&
                arfNeedsSwitch(op.getDirRegName()) &&
                m_model.supportsHwDeps())
            {
                warning("destination register ARF access requires {Switch} ThreadCtrl");
            }
            break;
        case Operand::Kind::MACRO:
            if (!i.isMacro()) {
                error("not a macro instruction");
            }
            break;
        default:
            break;
        }

        // conservatively check operands
        if (m_enabled_warnings & IGA_WARNINGS_TYPES) {
            auto ispec = i.getOpSpec();
            switch (ispec.format) {
            case OpSpec::BASIC_UNARY_REG:
            case OpSpec::BASIC_UNARY_REGIMM:
            case OpSpec::MATH_UNARY_REGIMM:
            case OpSpec::MATH_MACRO_UNARY_REG:
                checkOperandTypes(i);
                break;
            case OpSpec::BASIC_BINARY_REG_IMM:
            case OpSpec::BASIC_BINARY_REG_REG:
            case OpSpec::BASIC_BINARY_REG_REGIMM:
            case OpSpec::MATH_BINARY_REG_REGIMM:
            case OpSpec::MATH_MACRO_BINARY_REG_REG:
                checkOperandTypes(i);
                break;
            case OpSpec::TERNARY_REGIMM_REG_REGIMM:
            case OpSpec::TERNARY_MACRO_REG_REG_REG:
                checkOperandTypes(i);
                break;

            // punt on anything harder
            default:
                break;
            }
        }
    }

    void checkOperandTypes(const Instruction &i) {
        auto ispec = i.getOpSpec();
        for (
            size_t ti = 0;
            ti < sizeof(ispec.typeMappings)/sizeof(ispec.typeMappings[0]);
            ti++)
        {
            // type mappings are a zero-padded list of enum bitset pairs
            // the raw bits are stored in statically allocated structures
            // we construct them as EnumBitset<Type,uint32_t> and look
            // for a pair that matches all the source and destination
            // operand pairs
            auto tm = ispec.typeMappings[ti];
            const auto dsts = EnumBitset<Type>(tm.dsts);
            if (tm.dsts == 0) { // end of array (null dst)
                break;
            }
            if (!dsts.contains(i.getDestination().getType())) {
                // destination type mismatch, try the next destination
                continue;
            }
            const auto srcs = EnumBitset<Type>(tm.srcs);
            for (size_t s_ix = 0; s_ix < i.getSourceCount(); s_ix++) {
                if (srcs.contains(i.getSource(s_ix).getType())) {
                    // we found a valid mapping
                    return;
                }

                // WORKAROUNDS for busted BXML
                // TODO: fix these in the BXML
                // after they are all fixed, we can freshen BXML
                auto t = i.getSource(s_ix).getType();
                if (t == Type::UV || t == Type::V || t == Type::VF) {
                     return;
                }
            }
        }
        // we ran through all the type mappings without finding a match
        // throw a fit
        warning("invalid operand type combination for instruction");
    }

    void checkSrc(const Instruction &i, int srcIx) {
        const Operand &src = i.getSource(srcIx);
        const OpSpec &instSpec = i.getOpSpec();
        if ((m_enabled_warnings & IGA_WARNINGS_REGIONS) &&
            instSpec.supportsDestination() &&
            !instSpec.isSendOrSendsFamily())
        {
            checkRegRegioningRestrictions(
                i.getExecSize(),
                i.getDestination(),
                src);
        }
        Type srcType = src.getType();
        bool lblArg =
                src.getKind() == Operand::Kind::LABEL ||
                src.getKind() == Operand::Kind::IMMEDIATE;
        if ((m_enabled_warnings & IGA_WARNINGS_NORMFORM) &&
            srcType != Type::INVALID &&
            instSpec.hasImplicitSrcType(srcIx, lblArg, m_model.platform) &&
            instSpec.implicitSrcType(srcIx, lblArg, m_model.platform) != srcType)
        {
            warning("src type is not binary normal form");
        }
    }

    void checkRegRegioningRestrictions(
        ExecSize es, const Operand &dst, const Operand &src)
    {
        // only for the form:
        //  op (...)  dst.#<H> ... src.#<V;W,H>
        if (src.getKind() != Operand::Kind::DIRECT)
            return; // TODO: check indirect someday too
        int dstTypeSz = TypeSizeInBitsWithDefault(dst.getType(), 0) / 8, // to bytes
            srcTypeSz = TypeSizeInBitsWithDefault(src.getType(), 0) / 8; // to bytes
        if (dstTypeSz == 0 || srcTypeSz == 0)
            return; // e.g. Type::INVALID or a sub-byte type
        if (dst.getRegion().getHz() == Region::Horz::HZ_INVALID)
            return; // e.g. if the dst has no region
        if (!src.getRegion().isVWH())
            return; // e.g. if the src has no region
        int srcRgnV = static_cast<int>(src.getRegion().getVt()),
            srcRgnW = static_cast<int>(src.getRegion().getWi()),
            srcRgnH = static_cast<int>(src.getRegion().getHz());
        int execSize = ExecSizeToInt(es);
        // int dstReg = dst.getDirRegRef().regNum;
        // int srcReg = src.getDirRegRef().regNum;
        int srcSubReg = src.getDirRegRef().subRegNum;
        // int dstSubReg = dst.getDirRegRef().subRegNum;

        // Restriction 1.1: Where n is the largest element size in bytes for
        // any source or destination operand type, ExecSize * n must be <= 64.
        int restric_size = 64;
        if (execSize * srcTypeSz > restric_size) {
            warning(
                "register regioning restriction warning: "
                "ExecSize * sizeof(Type) exceeds 2 GRF\n"
                "see Programmer's Reference Manual (Restriction 1.1)");
        }
        // Restriction 1.2:
        // When the Execution Data Type is wider than the destination data type,
        // the destination must be aligned as required by the wider execution
        // data type and specify a HorzStride equal to the ratio in sizes of
        // the two data types. For example, a mov with a D source and B destination
        // must use a 4-byte aligned destination and a Dst.HorzStride of 4.
#if 0
        // TODO: this fails on:
        // mov      (8|M0)         r1.0<1>:b     r2.0<8;8,1>:b
        // The "Execution Data Type" ends up being 2, but the dst is 0
        int execTypeSz = srcTypeSz == 1 ? 2 : srcTypeSz; // "Execution Data Type" is max(2,..)
        int dstRgnH = static_cast<int>(dst.getRegion().getHz());
        bool dstIsDirect = dst.getKind() == Operand::Kind::DIRECT;
        if (dstIsDirect && execTypeSz > dstTypeSz) {
            if (dstTypeSz * dstRgnH != execTypeSz) {
                // e.g. the following violate this
                //  mov (8) r1.0<1>:b   r2...:d
                //  mov (8) r1.0<2>:b   r2...:d
                // but this is okay (correct)
                //  mov (8) r1.0<4>:b   r2...:d
                warning(
                    "register regioning restriction warning: "
                    "Dst.Hz * sizeof(Dst.Type) != Execution Data Type Size (destination misaligned for type)\n"
                    "see Programmer's Reference Manual (Restriction 1.2)");
            }
        }
#endif

        // Restriction 2.1: ExecSize must be greater than or equal to Width.
        if (execSize < srcRgnW) {
            //  mov (1) r1.0<1>:d   r2.0<8;8,1>:d
            warning(
                "register regioning restriction warning: "
                "ExecSize <= Src.W (partial row)\n"
                "see Programmer's Reference Manual (Restriction 2.1)");
        }

        // Restriction 2.2: If ExecSize = Width and HorzStride != 0,
        // VertStride must be set to Width * HorzStride.
        if (execSize == srcRgnW && srcRgnH != 0 && srcRgnV != srcRgnW * srcRgnH) {
            warning(
                "register regioning restriction warning: "
                "ExecSize == Src.W && Src.H != 0 && Src.V != Src.W * Src.H (vertical misalignment)\n"
                "see Programmer's Reference Manual (Restriction 2.2)");
        }

        // Restriction 2.3: If ExecSize = Width and HorzStride = 0,
        //   there is no restriction on VertStride.
        // The above checks this.

        // Restriction 2.4: If Width = 1, HorzStride must be 0 regardless of
        // the values of ExecSize and VertStride.
        if (execSize == 1 && srcRgnH != 0) {
            warning(
                "register regioning restriction warning: "
                "SIMD1 requires horizontal stride of 0 (scalar region access)\n"
                "see Programmer's Reference Manual (Restriction 2.4)");
        }

        // Restriction 2.5: If ExecSize = Width = 1, both VertStride and
        // HorzStride must be 0.
        if (execSize == 1 && srcRgnW == 1 && (srcRgnV != 0 || srcRgnH != 0)) {
            warning(
                "register regioning restriction warning: "
                "SIMD1 requires vertical and horiztonal to be 0 (scalar region access)\n"
                "see Programmer's Reference Manual (Restriction 2.5)");
        }

        // Restriction 2.6: If VertStride = HorzStride = 0, Width must be 1
        // regardless of the value of ExecSize.
        if (srcRgnV == 0 && srcRgnH == 0 && srcRgnW != 1) {
            warning(
                "register regioning restriction warning: "
                "If vertical stride and horizontal stride are 0, width must be 1.\n"
                "see Programmer's Reference Manual (Restriction 2.6)");
        }

        // Restriction 2.7: Dst.HorzStride must not be 0.
        // This will be checked higher up since this method gets called on
        // all source operands (and would duplicate error reports)

        // Restriction 2.8: VertStride must be used to cross GRF register boundaries.
        // This rule implies that elements within a 'Width' cannot cross GRF boundaries.
        //
        // Examples:
        //    LEGAL:     mov (16) r3:ub    r11.1<32;16,2>:ub
        //                e.g. the bytes accessed are
        //                  |.0.1.2.3.4.5.6.7.8.9.A.B.C.D.E.F|
        //    LEGAL:     mov  (4) r3:d     r11.1<8;4,2>:d
        //                e.g. the bytes accessed are
        //                  |....0000....1111....2222....3333|
        //
        //    ILLEGAL:   mov (8)  r3:d     r2.1<8;8,1>:d
        //                  |....0000111122223333444455556666|7777
        //                                                   ^ bad
        if (src.getDirRegName() == RegName::GRF_R) {
            // int slotsInReg = 32/srcTypeSz/srcRgnH;
            if (srcSubReg + (srcRgnW - 1) * srcRgnH >= restric_size / 2 /srcTypeSz) {
                // the last element is >= the maximum number of elements of
                // this type size
                warning(
                    "register regioning restriction warning: "
                    "Vertical stride must be used to cross GRF boundaries.\n"
                     "see Programmer's Reference Manual (Restriction 2.8)");
            }
        }

    }
};


void iga::CheckSemantics(
    const Kernel &k,
    ErrorHandler &err,
    uint32_t enabled_warnings)
{
    SemanticChecker checker(k.getModel(), &err, enabled_warnings);
    checker.checkKernel(k);
}


// checks basic IR structure (very incomplete)
struct SanityChecker {
    ErrorHandler *m_errHandler;
    const Instruction *m_inst;

    SanityChecker() : m_errHandler(nullptr), m_inst(nullptr) {}

    void checkKernel(const Kernel &k) {
        for (auto b : k.getBlockList()) {
            for (auto i : b->getInstList()) {
                checkInstruction(*i);
            }
        }
    }

    void checkInstruction(const Instruction &i) {
        m_inst = &i;
        if (i.getOpSpec().supportsDestination()) {
            checkDst(i.getDestination());
        } else if (i.getDestination().getKind() != Operand::Kind::INVALID) {
            IGA_ASSERT_FALSE("unsupported destination should be .kind=INVALID");
        }
        int srcs = i.getSourceCount();
        if (srcs > 0) {
            checkSrc(i, 0);
        }
        if (srcs > 1) {
            checkSrc(i, 1);
        }
        if (srcs > 2) {
            checkSrc(i, 2);
        }
        m_inst = nullptr;
    }

    void checkDst(const Operand &op) {
        switch (op.getKind()) {
        case Operand::Kind::DIRECT:
            IGA_ASSERT(op.getDirRegName() != RegName::INVALID, "invalid register");
            break;
        case Operand::Kind::INDIRECT:
            break;
        case Operand::Kind::MACRO:
            IGA_ASSERT(m_inst->isMacro(), "instruction is not macro");
            IGA_ASSERT(
                op.getMathMacroExt() != MathMacroExt::INVALID,
                "invalid accumulator for macro");
            break;
        default:
            IGA_ASSERT_FALSE("wrong kind for destination");
        }
    }

    void checkSrc(const Instruction &inst, int srcIx) {
        const Operand &op = inst.getSource(srcIx);
        switch (op.getKind()) {
        case Operand::Kind::DIRECT:
            IGA_ASSERT(op.getDirRegName() != RegName::INVALID, "invalid register");
            break;
        case Operand::Kind::INDIRECT:
            break;
        case Operand::Kind::MACRO:
            IGA_ASSERT(m_inst->isMacro(), "instruction is not macro");
            IGA_ASSERT(
                op.getMathMacroExt() != MathMacroExt::INVALID,
                "invalid accumulator for macro");
            break;
        case Operand::Kind::IMMEDIATE:
        case Operand::Kind::LABEL:
            break;
        default: {
            std::stringstream ss;
            auto loc = inst.getLoc();
            ss << loc.line << ":" << loc.col << ":[" << loc.offset << "]: wrong kind for src" << srcIx;
            IGA_ASSERT_FALSE(ss.str().c_str());
        }
        }
    }
};


void iga::SanityCheckIR(const Kernel &k) {
    SanityChecker checker;
    checker.checkKernel(k);
}


void iga::SanityCheckIR(const Instruction &i) {
    SanityChecker checker;
    checker.checkInstruction(i);
}
