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

Copyright (C) 2021 Intel Corporation

SPDX-License-Identifier: MIT

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

#include "FormatterJSON.hpp"
#include "IRToString.hpp"
#include "../IR/Messages.hpp"

#include <unordered_map>

using namespace iga;

class JSONFormatter: public BasicFormatter
{
protected:
    const Model                         &model;
    const FormatOpts&                    opts;
    const uint8_t                       *bits;
    int                                  currIndent = 2;

    // map instruction ID to all def/use pairs
    using DUMap = std::unordered_map<int,std::vector<const Dep*>>;
    DUMap                                depDefs;
    DUMap                                depUses;

    const RegSet                         EMPTY_SET;
public:
    JSONFormatter(std::ostream &o, const FormatOpts &os, const void *bs)
        : BasicFormatter(false, o)
        , opts(os)
        , model(os.model)
        , bits((const uint8_t *)bs)
        , EMPTY_SET(os.model)
    {
        o << std::boolalpha;
        if (opts.liveAnalysis) {
            for (const Dep &d : opts.liveAnalysis->deps) {
                if (d.def != nullptr) {
                    depDefs[d.def->getID()].emplace_back(&d);
                }
                if (d.use != nullptr) {
                    depUses[d.use->getID()].emplace_back(&d);
                }
            }
        }
    }
    Platform platform() const {return model.platform;}

    // emits an error value that'll blow up on JSON load
    void emitIrError(const char *what) {
        emit("IR.Error(\"", what, "\")");
    }

    void emitEscaped(const std::string &s) {
        for (size_t i = 0; i < s.size(); i++) {
            switch (s[i]) {
            case '\n': emit("\\n"); break;
            case '\t': emit("\\t"); break;
            case '\v': emit("\\v"); break;
            case '\f': emit("\\f"); break;
            case '\a': emit("\\a"); break;
            case '\"': emit("\\\""); break;
            case '\\': emit("\\\\"); break;
            default:   emit(s[i]); break;
            }
        }
    }

    void emitIndent() {
        for (int i = 0; i < currIndent; i++)
            emit(' ');
    }
    void withIndent(const std::function<void ()> &f) {
        currIndent += 2;
        f();
        currIndent -= 2;
    }

    void emitLabel(int32_t pc) {
        bool printed = false;
        if (opts.labeler) {
            const char *usrL = opts.labeler(pc, opts.labelerContext);
            if (usrL) {
                printed = true;
                emit(usrL);
            }
        }
        if (!printed) {
            std::stringstream ss;
            ss << std::setw(4) << std::dec << std::setfill('0') << pc;
            emit("L", ss.str());
        }
    }

    void emitKernel(const Kernel &k) {
        emit("{\n");
        emit("  \"version\":\"1.0\",");
        emit("  \"platform\":\"", model.names[0].str(),"\",");
        emit("  \"insts\":[\n");
        currIndent += 2;

        for (const Block *b : k.getBlockList()) {
            emitIndent();
            emit("{\"kind\":\"L\"");
                emit(",\"value\":\""); emitLabel(b->getPC()); emit("\"");
            if (opts.printInstPc) {
                emit(",\"pc\":", b->getPC());
            }
            emit("}");
            if (b != k.getBlockList().back() || !b->getInstList().empty())
                emit(",");
            emit("\n");

            for (const Instruction *iP : b->getInstList()) {
                emitInst(*iP);
                if (iP != b->getInstList().back() ||
                    b != k.getBlockList().back())
                    emit(",");
                emit("\n");
            }
        }

        emit("  ]\n");
        currIndent -= 2;
        emit("}\n");
        emit("\n");
    }

    std::vector<std::string> comments;

    void emitInst(const Instruction &i) {
        comments.clear();

        const bool isSend = i.getOpSpec().isSendOrSendsFamily();
        emitIndent();
        emit("{");
        emit("\"kind\":\"I\"");
        emit(", \"id\":", i.getID());
        if (opts.printInstPc) {
            emit(", \"pc\":", i.getPC());
        }

        if (opts.printLdSt) {
            if (!emitLdStInst(i)) {
                emitNormalInst(i);
            }
        } else {
            emitNormalInst(i);
        }

        if (platform() >= Platform::XE) {
            emitRegDist(i.getSWSB());
            emitSbid(i.getSWSB());
        } else {
            emit(", \"regDist\":null, \"sbid\":null");
        }
        emitInstOpts(i);

        bool printBits = opts.printInstBits && bits != nullptr;
        if (printBits) {
            emit(", \"encoding\":");
            const uint32_t *instBits = (const uint32_t *)(bits + i.getPC());
            emit('\"');
            if (!i.hasInstOpt(InstOpt::COMPACTED)) {
                emitHex(instBits[3], 8);
                emit('`');
                emitHex(instBits[2], 8);
                emit('`');
            }
            emitHex(instBits[1], 8);
            emit('`');
            emitHex(instBits[0], 8);
            emit('\"');
        }

        if (opts.printInstDefs &&
            opts.liveAnalysis &&
            !opts.liveAnalysis->sums.empty())
        {
            emit(", \"liveTotals\":{");
            const auto &totals = opts.liveAnalysis->sums[i.getID()];
            emit("\"r\":", totals.grfBytes, ",");
            emit("\"acc\":", totals.accBytes, ",");
            emit("\"f\":", totals.flagBytes, ",");
            emit("\"a\":", totals.indexBytes);
            emit("}");
        }

        emit(", \"comment\":");
        if (comments.empty()) {
            emit("null");
        } else {
            emit("\"");
            for (size_t i = 0; i < comments.size(); i++) {
                if (i > 0)
                    emit("; ");
                emitEscaped(comments[i]);
            }
            emit("\"");
        }
        emit("}");
    }

    void emitNormalInst(const Instruction &i) {
        std::string subfunc;
        switch (i.getOpSpec().op) {
        case Op::MATH: subfunc = ToSyntax(i.getSubfunction().math); break;
        case Op::SEND: subfunc = ToSyntax(i.getSubfunction().send); break;
        case Op::SENDC: subfunc = ToSyntax(i.getSubfunction().send); break;
        case Op::SYNC: subfunc = ToSyntax(i.getSubfunction().sync); break;
        case Op::BFN:
            if (opts.syntaxBfnSymbolicFunctions) {
                // decode binary function names to symbolic
                // bfn.(s0&s1|~s2)
                subfunc += "(";
                subfunc += i.getBfnFc().c_str();
                subfunc += ")";
            } else {
                // use a raw value
                // emit<uint32_t>(i.getBfnFc().value);
                subfunc = fmtHex((int)i.getBfnFc().value, 2);
            }
            break;
        case Op::DPASW:
        case Op::DPAS:
            subfunc = ToSyntax(i.getDpasFc());
            break;
        default: break;
        }

        emitPredOpSubfuncExecInfoFlagReg(
            i, i.getOpSpec().mnemonic.str(), subfunc);
        // implicit accumulator def/uses
        emit(", \"other\":");
        if (opts.liveAnalysis) {
            emit("{");
            RegSet accIn(model);
            accIn.addSourceImplicit(i);
            emitDepInputs(i, accIn);
            // RegSet accOu(model);
            // accOu.addDestinationImplicitAccumulator(i);
            // emit(", "); emitDepOutputs(i, accOu);
            emit("}");
        } else {
            emit("null");
        }

        emit(",\n");

        withIndent([&] {
            emitIndent(); emit("\"dst\":");
            emitDst(i); emit(",\n");
            emitIndent(); emit("\"srcs\":[\n");
            withIndent([&] {
                for (unsigned srcIx = 0; srcIx < i.getSourceCount(); srcIx++) {
                    emitIndent(); emitSrc(i, srcIx);
                    if (srcIx != i.getSourceCount() - 1)
                        emit(",\n");
                }
                // if it's a send, create fake extra send parameters
                if (i.getOpSpec().isSendOrSendsFamily()) {
                    auto emitDesc = [&](SendDesc sd) {
                        emit(",\n"); emitIndent();
                        emit("{");
                        RegSet rs(model);
                        if (sd.type == SendDesc::Kind::REG32A) {
                            emitKindField(Operand::Kind::DIRECT);
                            emit(", \"reg\":");
                            emitReg(RegName::ARF_A, sd.reg);
                            rs.setSrcRegion(RegName::ARF_A, sd.reg,
                                Region::SRC010, 1, 4);
                        } else {
                            emitKindField(Operand::Kind::IMMEDIATE);
                            emit(", \"value\":\"");
                            emitHex(sd.imm);
                            emit("\"");
                        }
                        emit(", \"rgn\":null");
                        emit(", \"type\":null");
                        emit(", "); emitDepInputs(i, rs);
                        emit("}");
                    };
                    emitDesc(i.getExtMsgDescriptor());
                    emitDesc(i.getMsgDescriptor());
                }
            });
            emit("\n"); emitIndent(); emit("]");
        });
    }


    /////////////////////////////////////////////////////////////////////////
    bool emitLdStInst(const Instruction &i) {
        const auto desc = i.getMsgDescriptor();
        if (desc.isReg()) {
            // given a register descriptor, we've no hope of decoding the op
            // TODO: once we have dataflow values maybe we can...
            return false;
        }
        const auto exDesc = i.getExtMsgDescriptor();
        const auto sfid = i.getSendFc();
        const auto di =
            tryDecode(platform(), sfid, i.getExecSize(),
                exDesc, desc, nullptr);
        if (!di) {
            // if message decode failed fallback to the canonical send syntax
            return false;
        } else if (!sendOpSupportsSyntax(platform(), di.info.op, sfid)) {
            // if decode succeeded but the syntax isn't supported, then bail
            return false;
        }

        emitPredOpSubfuncExecInfoFlagReg(
            i, di.syntax.mnemonic, di.syntax.controls);

        emit(",\n");
        withIndent([&] {
            emitIndent();
            if (!di.syntax.isStore()) {
                // there is an explicit destination
                const Operand &dst = i.getDestination();
                emit("\"dst\":{");
                emitSendDataRegDst(i,
                    dst.getDirRegName(), dst.getDirRegRef().regNum,
                    i.getDstLength());
                emit("}");

            } else {
                emit("\"dst\":null");
            }
            emit(",\n");

            /////////////////////////////////////////////
            // sources
            emitIndent(); emit("\"srcs\":[\n");
            withIndent([&] {
                // everyone has src0 as an address
                emitIndent();
                emit("{\"kind\":\"AD\"");
                //
                emit(", \"surf\":");
                emitAddrSurfInfo(i, di.info);
                //
                emit(", \"scale\":1");
                emit(", \"addr\":");
                    emitSendPayloadSrc(i, 0);

                emit(", \"offset\":");
                emit(di.info.immediateOffset);
                emit("},\n");

                //////////////////////////////////////
                // src1 (if not the old unary send)
                if (i.getSourceCount() > 1) {
                    emitIndent();
                    emitSendPayloadSrc(i, 1, "DA");
                    emit("\n");
                }
            });

            emitIndent(); emit("]"); // end of srcs:[..]
        });

        return true;
    }


    /////////////////////////////////////////////////////////////////////////
    // op+subfunc+exec size and exec offset
    void emitPredOpSubfuncExecInfoFlagReg(
        const Instruction &i, std::string mnemonic, std::string subop)
    {
        emitPred(i);

        emit(", \"op\":\"", mnemonic, "\"");
        if (subop.empty()) {
            emit(", \"subop\":null");
        } else {
            auto sf = subop[0] == '.' ? subop.substr(1) : subop;
            emit(", \"subop\":\"", sf, "\"");
        }

        emitExecInfo(i);

        if (i.hasFlagModifier()) {
            emit(", \"fm\":{\"cond\":\"", ToSyntax(i.getFlagModifier()), "\"");
            // emit(", ");
            // RegSet rs(model);
            // rs.addFlagModifierOutputs(i);
            // emitDepOutputs(i, rs);
            emit("}");
        } else {
            emit(", \"fm\":null");
        }

        emitFlagReg(i);
    }

    void emitExecInfo(const Instruction &i) {
        emit(", \"es\":", ToSyntax(i.getExecSize()));
        emit(", \"eo\":", 4*(int)(i.getChannelOffset()));
    }

    // pred:{func:[null|""|".any"|".all"|...],inv:[true|false]"",wren:[true|false]}
    void emitPred(const Instruction &i) {
        Predication p = i.getPredication();
        emit(", \"pred\":");
        if (i.hasPredication()) {
            emit("{");
            emit("\"inv\":", p.inverse);
            emit(", \"func\":\"", ToSyntax(p.function), "\"");
            emit(", ");
            RegSet rs(model); rs.addPredicationInputs(i);
            emitDepInputs(i, rs);
            emit("}");
            // TODO: handle SEL
        } else {
            emit("null");
        }
        //
        emit(", \"wren\":", i.getMaskCtrl() == MaskCtrl::NOMASK);
    }

    // {flag:{reg:...}}
    void emitFlagReg(const Instruction &i) {
        emit(", \"freg\":");
        if (i.hasPredication() || (i.hasFlagModifier() && !i.is(Op::SEL))) {
            emitReg(RegName::ARF_F, i.getFlagReg());
        } else {
            emit("null");
        }
    }

    // dst:{kind=..., REST, sat:T|F, defs:..., uses:...}
    //  REST: if kind == R (direct)
    //     reg:IR.Regs.r13 or Regs.r(r13,4)
    //     rgn:IR.Rgns.DST1
    //     type:IR.Types.D
    void emitDst(const Instruction &i) {
        const OpSpec &os = i.getOpSpec();
        if (os.isSendOrSendsFamily()) {
            emitSendDst(i);
            return;
        }
        const Operand &dst = i.getDestination();
        if (!i.getOpSpec().supportsDestination()) {
            emit("null");
            return;
        }

        emit("{");
        emitKindField(dst.getKind());
        switch (dst.getKind()) {
        case Operand::Kind::DIRECT:
            emit(", \"reg\":");
                emitReg(dst.getDirRegName(), dst.getDirRegRef());
            break;
        case Operand::Kind::MACRO:
            emit(", \"reg\":");
                emitReg(dst.getDirRegName(), dst.getDirRegRef());
            emit(", ");
            emitMathMacroExtField(dst.getMathMacroExt());
            break;
        case Operand::Kind::INDIRECT:
            emit(", \"areg\":");
                emitReg(RegName::ARF_A, dst.getIndAddrReg());
            emit(", \"aoff\":", dst.getIndImmAddr());
            break;
        default:
            break;
        }
        emit(", \"sat\":", dst.getDstModifier() == DstModifier::SAT);
        emit(", \"rgn\":");
        if (os.hasImplicitDstRegion(i.isMacro())) {
            emit("null");
        } else {
            emitRgn(i, dst.getRegion(), true);
        }
        emit(", \"type\":"); emitType(dst.getType());

        // def/uses
        emit(",");
        RegSet rs(model);
        rs.addDestinationInputs(i);
        emitDepInputs(i, rs);
        emit("}");
    }


    void emitMathMacroExtField(MathMacroExt mme) {
        emit("\"mme\":\"", ToSyntax(mme).substr(1), "\"");
    }


    void emitSrc(const Instruction &i, int srcIx) {
        const OpSpec &os = i.getOpSpec();
        if (os.isSendOrSendsFamily()) {
            emitSendSrc(i, srcIx);
            return;
        }
        const Operand &src = i.getSource(srcIx);
        emit("{");
        emitKindField(src.getKind());
        bool immOrLbl = false;
        switch (src.getKind()) {
        case Operand::Kind::DIRECT:
            emitSrcModifierField(src.getSrcModifier());
            emit(", \"reg\":");
                emitReg(src.getDirRegName(), src.getDirRegRef());
            emit(", \"rgn\":");
                emitSrcRgn(i, srcIx);
            break;
        case Operand::Kind::MACRO:
            emitSrcModifierField(src.getSrcModifier());
            emit(", \"reg\":");
                emitReg(src.getDirRegName(), src.getDirRegRef());
            emit(", ");
                emitMathMacroExtField(src.getMathMacroExt());
            emit(", \"rgn\":");
                emitSrcRgn(i, srcIx);
            break;
        case Operand::Kind::INDIRECT:
            emitSrcModifierField(src.getSrcModifier());
            emit(", \"areg\":");
                emitReg(RegName::ARF_A, src.getIndAddrReg());
            emit(", \"aoff\":", src.getIndImmAddr());
            break;
        case Operand::Kind::IMMEDIATE:
        {
            emit(", \"value\":\"");
            auto imm = src.getImmediateValue();
            switch (src.getType()) {
            case Type::UB:  emitHex(imm.u8); break;
            case Type::UW:  emitHex(imm.u16); break;
            case Type::UD:  emitHex(imm.u32); break;
            case Type::UQ:  emitHex(imm.u64); break;
            case Type::B:   emitDecimal(imm.s8); break;
            case Type::W:   emitDecimal(imm.s16); break;
            case Type::D:   emitDecimal(imm.s32); break;
            case Type::Q:   emitDecimal(imm.s64); break;
            case Type::BF:  emitHex(imm.u16); break;
            case Type::HF:
                if (opts.hexFloats) {
                    emitHex(imm.u16);
                } else {
                    FormatFloat(o, imm.u16);
                }
                break;
            case Type::F:
            case Type::TF32:
                if (opts.hexFloats) {
                    emitHex(imm.u32);
                } else {
                    emitFloat(imm.f32);
                }
                break;
            case Type::DF:
                if (opts.hexFloats) {
                    emitHex(imm.u64);
                } else {
                    emitFloat(imm.f64);
                }
                break;
            case Type::BF8:  emitHex(imm.u8); break;
            case Type::V:    emitHex(imm.u32); break;
            case Type::UV:   emitHex(imm.u32); break;
            case Type::VF:   emitHex(imm.u32); break;
            default:
                emitIrError("invalid type for imm");
            }
            emit("\"");
            immOrLbl = true;
            break;
        }
        case Operand::Kind::LABEL:
        {
            const Block *b = src.getTargetBlock();
            emit(", \"target\":\"");
            emitLabel(b ? b->getPC() : src.getImmediateValue().s32);
            emit("\"");
            immOrLbl = true;
            break;
        }
        default:
            break;
        }

        emit(", \"type\":");
        if (os.hasImplicitSrcType(srcIx, immOrLbl)) {
            emit("null");
        } else {
            emitType(src.getType());
        }

        // even immediate fields will have this; it'll just be empty
        emit(", ");
        RegSet rs(model);
        rs.addSourceOperandInput(i, srcIx);
        emitDepInputs(i, rs);

        emit("}");
    }

    void emitSendDst(const Instruction &i) {
        const Operand &dst = i.getDestination();
        emit("{");
        if (i.getDstLength() >= 0) {
            // new style send (r13:4) (take bits from desc)
            emitSendDataRegDst(i,
                dst.getDirRegName(),
                dst.getDirRegRef().regNum,
                i.getDstLength());
        } else {
            emit("\"kind\":\"RD\"");
            emit(", \"reg\":");
                emitReg(dst.getDirRegName(), dst.getDirRegRef());
        }
        emit("}");
    }

    void emitSrcModifierField(SrcModifier sm) {
        emit(", \"mods\":",
            sm == SrcModifier::NEG ? "\"n\"" :
            sm == SrcModifier::ABS ? "\"a\"" :
            sm == SrcModifier::NEG_ABS ? "\"na\"" :
            "\"\"");
    }

    void emitKindField(Operand::Kind k) {
        emit("\"kind\":");
        switch (k) {
        case Operand::Kind::DIRECT:    emit("\"RD\""); break;
        case Operand::Kind::MACRO:     emit("\"RM\""); break;
        case Operand::Kind::INDIRECT:  emit("\"RI\""); break;
        case Operand::Kind::LABEL:     emit("\"LB\""); break;
        case Operand::Kind::IMMEDIATE: emit("\"IM\""); break;
        default: emitIrError("bad operand kind");
        }
    }

    void emitSrcRgn(const Instruction &i, int srcIx) {
        const Operand &src = i.getSource(srcIx);
        if (i.getOpSpec().hasImplicitSrcRegion(
            srcIx, i.getExecSize(), i.isMacro()))
        {
            emit("null");
        } else {
            emitRgn(i, src.getRegion(), false);
        }
    }

    void emitSendSrc(const Instruction &i, int srcIx) {
        const Operand &src = i.getSource(srcIx);

        if (srcIx == 0 && i.getSrc0Length() >= 0) {
            emit("{");
            const auto di =
                tryDecode(platform(), i.getSubfunction().send, i.getExecSize(),
                    i.getExtMsgDescriptor(), i.getMsgDescriptor(),
                    nullptr);
            emit("\"kind\":\"AD\"");
            emit(", \"surf\":");
            if (di) {
                comments.push_back(di.info.description);
                emitAddrSurfInfo(i, di.info);
            } else {
                emit("null");
            }
            emit(", \"scale\":1");
            emit(", \"addr\":");
            emitSendPayloadSrc(i, 0);
            if (di) {
                // e.g. legacy scratch
                emit(", \"offset\":", di.info.immediateOffset);
            } else {
                // shouldn't be a reg, but we emit something
                emit(", \"offset\":0");
            }
            emit("}");
        } else if (srcIx == 1 && i.getSrc1Length() >= 0) {
            emitSendPayloadSrc(i, 1, "DA");
        } else {
            // old send operand (treat as raw direct register access)
            emit("\"kind\":\"RD\"");
            emit(", \"reg\":");
                emitReg(src.getDirRegName(), src.getDirRegRef());
            emit(", \"rgn\":null");
            emit(", \"type\":null");
            // the best we can do is guess it's 1 without decoding the message
            RegSet rs(model);
            rs.addSourceOperandInput(i, srcIx);
            emitSendPayloadDeps(i,
                rs,
                src.getDirRegName(),
                src.getDirRegRef().regNum, 1, true);
        }
    }

    void emitAddrSurfInfo(const Instruction &i, const MessageInfo &mi) {
        emit("{\"type\":");
        switch (mi.addrType) {
        case AddrType::FLAT: emit("\"flat\""); break;
        case AddrType::BTI:  emit("\"bti\""); break;
        case AddrType::BSS:  emit("\"bss\""); break;
        case AddrType::SS:   emit("\"ss\""); break;
        default: emitIrError("invalid surface type");
        }

        emit(", \"offset\":");
        RegSet surfOffDeps(model);
        if (mi.surfaceId.isReg()) {
            surfOffDeps.setSrcRegion(RegName::ARF_A,
                mi.surfaceId.reg,
                Region::SRC010, 1, 4);
            emitReg(RegName::ARF_A, mi.surfaceId.reg);
        } else {
            emitDecimal(mi.surfaceId.imm);
        }
        emit(", ");
        emitDepInputs(i, surfOffDeps);
        emit("}"); // end of surf:{...}
    }

    void emitSendPayloadSrc(
        const Instruction &i, int srcIx, const char *kind = nullptr)
    {
        const Operand &src = i.getSource(srcIx);
        RegName regName = src.getDirRegName();
        int regStart = src.getDirRegRef().regNum;
        int regCount = srcIx == 0 ? i.getSrc0Length() : i.getSrc1Length();
        emit("{");
        if (kind) {
            emit("\"kind\":\"",kind,"\", ");
        }
        emit("\"reg\":");
        emitReg(regName, regStart);
        emit(", \"len\":", regCount);
        RegSet rs(model);
        rs.addSourceOperandInput(i, srcIx);
        emitSendPayloadDeps(i, rs, regName, regCount, regCount, true);
        emit("}");
    }

    void emitSendDataRegDst(
        const Instruction &i, RegName dataReg, int regNum, int regLen)
    {
        emit("\"kind\":\"DA\"");
        emitSendPayloadFields(dataReg, regNum, regLen);
        emitSendPayloadDeps(i, EMPTY_SET, dataReg, regNum, regLen, false);
    }

    void emitSendPayloadFields(RegName reg, int regNum, int numRegs)
    {
        emit(", \"reg\":");
        emitReg(reg, regNum);
        emit(", \"len\":", numRegs);
    }
    void emitSendPayloadDeps(
        const Instruction &i,
        const RegSet &regStats,
        RegName reg, int regNum, int numRegs, bool isRead)
    {
        if (isRead) {
            emit(", ");
            emitDepInputs(i, regStats);
        } else {
            // emit(", ");
            // emitDepOutputs(i, rs);
        }
    }

    void emitRgn(const Instruction &i, Region r, bool dst = false) {
        if (!dst) {
            // fix ternary regions
            if (r.getVt() == Region::Vert::VT_INVALID &&
                r.getWi() == Region::Width::WI_INVALID)
            {
                // ternary src2
                //    officially it was: w = 0 if h is 0 or E; v = w * h
                // use normalized regions here to simplify
                //   <0> ==> <0;1,0>
                //   <K> ==> <K;1,0>
                auto hz = r.getHz();
                // auto es = i.getExecSize();
                if (hz == Region::Horz::HZ_0) {
                    r = Region::SRC010;
                } else {
                    r = Region::SRC110;
                    r.set(Region::Vert(hz));
                }
            } else if (r.getWi() == Region::Width::WI_INVALID) {
                // ternary src0/src1
                //  w = 1 when v & h are 0
                //    = v when h = 0
                //    = v/h otherwise
                auto vt = r.getVt();
                auto hz = r.getHz();
                if (vt == Region::Vert::VT_0 && hz == Region::Horz::HZ_0) {
                    r.set(Region::Width::WI_1);
                } else if (hz == Region::Horz::HZ_0) {
                    r.set(Region::Width(int(vt)));
                } else {
                    r.set(Region::Width(int(vt)/int(hz)));
                }
            }
        }
        emitRgn(r, dst);
    } // emitRgn

    void emitRgn(Region r, bool dst = false) {
        if (dst) {
#if IGA_SHORTHAND_JSON
            emit("IR.Rgns.");
            switch (r.getHz()) {
            case Region::Horz::HZ_1: emit("d1"); break;
            case Region::Horz::HZ_2: emit("d2"); break;
            case Region::Horz::HZ_4: emit("d4"); break;
            default: emit("IR.Error(\"invalid dst region\")");
            }
#else
            emit("{\"Hz\":", int(r.getHz()), "}");
#endif
        }
        else
        {
#if IGA_SHORTHAND_JSON
            emit("IR.Rgns.");
            if (r == Region::SRC010) {
                emit("s0_1_0");
            } else if (r == Region::SRC110) {
                emit("s1_1_0");
            } else if (r == Region::SRC210) {
                emit("s2_1_0");
            } else if (r == Region::SRC410) {
                emit("s4_1_0");
            } else if (r.getVt() == Region::Vert::VT_VxH) {
                // <w,h> region for indrect access
                // let the JS library sort it out
                emit("sVxH(", int(r.getWi()), ",",
                    int(r.getHz()), ")");
            } else {
                emit("sVWH(",
                    int(r.getVt()), ",",
                    int(r.getWi()), ",",
                    int(r.getHz()), ")");
            }
#else
            emit("{");
            if (r.getVt() == Region::Vert::VT_VxH) {
                emit("\"Vt\":null");
            } else {
                emit("\"Vt\":", int(r.getVt()));
            }
            emit(",\"Wi\":", int(r.getWi()), ",\"Hz\":", int(r.getHz()));
            emit("}");
#endif
        }
    }

    void emitType(Type t) {
        if (t == Type::INVALID) {
            emit("null");
            return;
        }
#if IGA_SHORTHAND_JSON
        emit("IR.Types."); // e.g. IR.Types.UQ
        auto s = ToSyntax(t); // ":uq"
        for (size_t i = 1; i < s.size(); i++)
            emit((char)toupper(s[i]));
#else
        emit("\"", ToSyntax(t).substr(1), "\""); // Type::UQ => "uq"
#endif
    }

    void emitRegDist(const SWSB &di) {
        emit(", \"regDist\":");
        auto emitPipeDist = [&] (const char *pfx) {
            emit("\"", pfx, "@", (int)di.minDist, "\"");
        };
        switch (di.distType) {
        case SWSB::DistType::REG_DIST:        emitPipeDist(""); break;
        case SWSB::DistType::REG_DIST_ALL:    emitPipeDist("A"); break;
        case SWSB::DistType::REG_DIST_FLOAT:  emitPipeDist("F"); break;
        case SWSB::DistType::REG_DIST_INT:    emitPipeDist("I"); break;
        case SWSB::DistType::REG_DIST_LONG:   emitPipeDist("L"); break;
        case SWSB::DistType::REG_DIST_MATH:   emitPipeDist("M"); break;
        default: emit("null"); break;
        }
    }

    void emitSbid(const SWSB &di) {
        emit(", \"sbid\":");
        auto emitSwsbEvent = [&] (const char *sfx) {
            emit("\"$", (int)di.sbid, sfx, "\"");
        };
        switch (di.tokenType) {
        case SWSB::TokenType::DST:  emitSwsbEvent(".dst"); break;
        case SWSB::TokenType::SRC:  emitSwsbEvent(".src"); break;
        case SWSB::TokenType::SET:  emitSwsbEvent(""); break;
        default: emit("null"); break;
        }
    }

    void emitInstOpts(const Instruction &i) {
        static const InstOpt ALL_INST_OPTS[] {
            InstOpt::ACCWREN,
            InstOpt::ATOMIC,
            InstOpt::BREAKPOINT,
            InstOpt::COMPACTED,
            InstOpt::EOT,
            InstOpt::NOCOMPACT,
            InstOpt::NODDCHK,
            InstOpt::NODDCLR,
            InstOpt::NOPREEMPT,
            InstOpt::NOSRCDEPSET,
            InstOpt::SWITCH,
            InstOpt::SERIALIZE,
            InstOpt::EXBSO,
            InstOpt::CPS,
        };

        InstOptSet ios = i.getInstOpts();
        emit(", \"opts\":[");
        bool first = true;
        auto emitSeparator = [&]() {
            if (first)
                first = false;
            else
                emit(",");
        };
        for (size_t i = 0;
            i < sizeof(ALL_INST_OPTS)/sizeof(ALL_INST_OPTS[0]) && !ios.empty();
            i++)
        {
            if (ios.contains(ALL_INST_OPTS[i])) {
                emitSeparator();
                ios.remove(ALL_INST_OPTS[i]);
                emit("\"", ToSyntax(ALL_INST_OPTS[i]), "\"");
            }
        }

        const auto &di = i.getSWSB();
        // special token and dist/token won't co-exist in the same swsb
        // no need to insert "," after this since there must not be
        // dist/token swsb
        if (di.hasSpecialToken()) {
            IGA_ASSERT(!di.hasDist() && !di.hasToken(), "malformed SWSB IR");
            if (di.spToken == SWSB::SpecialToken::NOACCSBSET) {
                emitSeparator();
                emit("NoAccSBSet");
            }
        }
        emit("]");
    }

    void emitReg(RegName rn, RegRef rr) {
#if IGA_SHORTHAND_JSON
        bool hasShortName =
            rn == RegName::GRF_R ||
            rn == RegName::ARF_A || rn == RegName::ARF_ACC ||
            rn == RegName::ARF_NULL;
        if (rr.subRegNum == 0 && hasShortName) {
            // IR.Regs.r13 is a direct reference
            const RegInfo *ri = model.lookupRegInfoByRegName(rn);
            if (ri == nullptr) {
                emitIrError("unsupported RegName");
            } else if (ri->hasRegNum()) {
                emit("IR.Regs.", ToSyntax(rn), rr.regNum);
            } else if (rn == RegName::ARF_NULL) {
                emit("IR.Regs.null_0"); // since "null" is a keyword
            } else {
                emit("IR.Regs.", ToSyntax(rn));
            }
        } else if (rn == RegName::ARF_F) {
            emit("IR.Regs.f", rr.regNum, "_", rr.subRegNum);
        } else {
            // IR.Regs.reg("r",13,4) creates a new object to r13.4
            emit("IR.Regs.reg(\"", ToSyntax(rn), "\",", rr.regNum, ",",
                rr.subRegNum, ")");
        }
#else
        // use full JSON syntax (no shortcuts)
        emit("{"
            "\"rn\":\"", ToSyntax(rn), "\","
            "\"r\":", rr.regNum, ",",
            "\"sr\":", rr.subRegNum, "}");
#endif
    }
    void emitReg(RegName rn, int regNum, int sr = 0) {
        emitReg(rn, RegRef((int16_t)regNum, (int16_t)sr));
    }

    /*
    /////////////////////////////////////////////////////////////////////////
    // call this for operands that are instruction outputs
    void emitDepOutputs(const Instruction &i, const RegSet &rs) {
        // 'i' is writing 'rs' emit all pairs where this is the
        // def and report them as uess
        emit("\"uses\":[");
        if (opts.liveAnalysis) {
            emit("]");
            return;
        }
        // the users of 'rs' are all those that use this set
        bool first = true;
        for (const Dep *d : depDefs[i.getID()]) {
            if (d->use != nullptr && rs.intersects(d->values)) {
                if (first)
                    first = false;
                else
                    emit(",");
                emit(d->use->getID());
            }
        }
        emit("]");
    }
    */

    // call this for operands that are instruction inputs
    void emitDepInputs(const Instruction &i, const RegSet &rs) {
        // 'i' is writing 'rs' emit all pairs where this is the
        // def and report them as uess
        emit("\"defs\":[");
        if (opts.liveAnalysis == nullptr) {
            emit("]");
            return;
        }
        // the users of 'rs' are all those that use this set
        bool first = true;
        auto c = depUses[i.getID()];
        for (const Dep *d : c) {
            if (d->def != nullptr && rs.intersects(d->values)) {
                if (first)
                    first = false;
                else
                    emit(",");
                emit(d->def->getID());
            }
        }
        emit("]");
    }
};

void iga::FormatJSON(
    std::ostream &o,
    const FormatOpts &opts,
    const Kernel &k,
    const void *bits)
{
    JSONFormatter(o, opts, bits).emitKernel(k);
}

void iga::FormatInstructionJSON(
    std::ostream &o,
    const FormatOpts &opts,
    const Instruction &i,
    const void *bits)
{
    JSONFormatter(o, opts, bits).emitInst(i);
}
