/*===================== 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 "Backend/Native/InstEncoder.hpp"
#include "Backend/Native/Interface.hpp"
#include "ColoredIO.hpp"
#include "Frontend/Formatter.hpp"
#include "InstDiff.hpp"
#include "strings.hpp"
#include "bits.hpp"

#include <algorithm>
#include <bitset>
#include <iostream>
#include <iomanip>
#include <map>
#include <sstream>
#include <string>


using namespace iga;

static std::string disassembleInst(
    Platform platform,
    bool useNativeDecoder,
    uint32_t fmtOpts,
    size_t fromPc,
    const void *bits)
{
    ErrorHandler eh;
    std::stringstream ss;
    FormatOpts fopts(platform);
    fopts.addApiOpts(fmtOpts);
    fopts.setSWSBEncodingMode(
        iga::Model::LookupModel(platform)->getSWSBEncodeMode());
    FormatInstruction(eh, ss, fopts, fromPc, bits, useNativeDecoder);

    return ss.str();
}
static std::string fmtPc(const MInst *mi, size_t pc)
{
    std::stringstream ssPc;
    ssPc << "[";
    if (mi) {
        ssPc << "0x" << std::setfill('0') << std::setw(3) <<
            std::uppercase << std::hex << pc;
    } else {
        ssPc << std::setw(3) << "EOF";
    }
    ssPc << "] ";
    return ssPc.str();
}
static std::string fmtHexValue(uint64_t val)
{
    return fmtHex(val);
}
static std::string fmtBitRange(size_t off, size_t len)
{
    std::stringstream ssOff;  // [...:..]
    if (len != 1) {
        ssOff << "[" << (off + len - 1) << ":" << off << "]";
    } else {
        ssOff << "[" << off << "]";
    }
    return ssOff.str();
}
static std::string fmtBitRange(const Fragment &f)
{
    return fmtBitRange(f.offset, f.length);
}
static std::string fmtSize(const Fragment &f)
{
    std::stringstream ssSize;
    ssSize << "(" << f.length << ")";
    return ssSize.str();
}

static const char *UNMAPPED_FIELD_NAME = "<UNMAPPED>";
static const size_t FIELD_PC_WIDTH = 7; // [0x200]
static const size_t FIELD_NAME_WIDTH = 20; // Src.Type
static const size_t FIELD_OFFSET_WIDTH = 10; // [34:32]
static const size_t FIELD_SIZE_WIDTH = 5; // (32)
static const size_t FIELD_VALUE_INT_WIDTH = 14; // 0x1232
static const size_t FIELD_VALUE_STR_WIDTH = 20; // :d

static FragmentList decodeFieldsWithWarnings(
    const Model &model,
    std::ostream &os,
    Loc loc,
    const MInst *mi,
    bool &success,
    const char *leftOrRight = nullptr)
{
    FragmentList fields;
    ErrorHandler errHandler;
    native::DecodeFields(loc, model, (const void *)mi, fields, errHandler);
    for (auto &e : errHandler.getErrors()) {
        std::stringstream ss;
        ss << "PC" << e.at.offset << ". " << e.message << "\n";
        emitYellowText(os, ss.str());
        success = false;
    }
    auto findFieldsContaining =
        [&] (size_t bit_ix)
    {
        std::vector<Fragment*> matching;
        for (auto &fi : fields) {
            if (bit_ix >= (size_t)fi.first.offset &&
                bit_ix < (size_t)fi.first.offset + (size_t)fi.first.length)
            {
                matching.push_back(&fi.first);
            }
        }
        return matching;
    };

    std::stringstream warningStream;
    size_t iLen = mi->isCompact() ? 8 : 16;
    // check for field overlap
    for (size_t bitIx = 0; bitIx < 8*iLen;) {
        // find all the fields that overlap at this bit index
        auto matching = findFieldsContaining(bitIx);
        // no fields map here or multiple matching, issue missing field warning
        if (matching.size() == 1) {
            // exactly one field maps this index, step forward
            bitIx++;
        } else {
            // find the end of this missing/overlapped segment
            size_t endIx = bitIx + 1;
            for (; endIx < 8*iLen; endIx++) {
                auto m = findFieldsContaining(endIx);
                if (m != matching) {
                    break;
                }
            }
            if (!matching.empty()) {
                // TODO: remove this: do it at a higher level when we
                // are emitting the columns
                if (leftOrRight) {
                    warningStream << "in " << leftOrRight << " ";
                }
                warningStream << "BITS";
                warningStream << fmtBitRange(bitIx, endIx - bitIx) << ": ";
                warningStream << "PC:" << loc.offset <<
                    ": multiple fields map this range\n";
                success = false;
            } else {
                // insert an ERROR field here since nothing maps there
                Fragment errorField = Fragment(
                    UNMAPPED_FIELD_NAME,
                    (int)bitIx,
                    (int)(endIx - bitIx));
                FragmentList::iterator itr = fields.begin();
                for (;
                    itr != fields.end() &&
                        itr->first.offset < errorField.offset;
                    itr++)
                    ;
                fields.insert(itr,
                    std::pair<Fragment,std::string>(errorField,""));
                success = false;
            }
            bitIx = endIx; // sanity restarts (or some new error)
        }
    }
    if (!warningStream.str().empty()) {
        success = false;
        emitRedText(os, warningStream.str());
    }

    return fields;
}



static void decodeFieldHeaders(std::ostream &os)
{
    os << std::setw(FIELD_PC_WIDTH) << std::left << "PC";
    os << " ";
    os << std::setw(FIELD_NAME_WIDTH) << std::left << "FIELD";
    os << " ";
    os << std::setw(FIELD_OFFSET_WIDTH) << " "; // [...:..]
    os << " ";
    os << std::setw(FIELD_SIZE_WIDTH) << " "; // (..)
    os << " ";
    os << std::setw(FIELD_VALUE_INT_WIDTH + 1 + FIELD_VALUE_STR_WIDTH) <<
        std::right << "VALUE";
    os << "\n";
}

static bool decodeFieldsForInst(
    bool useNativeDecoder,
    uint32_t fmtOpts,
    std::ostream &os,
    size_t pc,
    const Model &model,
    const MInst *mi)
{
    bool success = true;

    auto syntax = disassembleInst(
        model.platform, useNativeDecoder, fmtOpts, pc, (const void *)mi);
    os << fmtPc(mi, pc) << " " <<  syntax << "\n";
    os.flush();

    FragmentList fields =
        decodeFieldsWithWarnings(model, os, Loc((uint32_t)pc), mi, success);

    auto fieldOverlaps =
        [&] (const Fragment &f) {
            for (const auto &fv : fields) {
                const Fragment *fp = &fv.first;
                if (fp != &f && f.overlaps(*fp))
                    return true;
            }
            return false;
        };

    for (const FragmentListElem &fv : fields) {
        const Fragment &f = fv.first;
        uint64_t val = mi->getFragment(f);
        std::stringstream ss;
        ss << std::setw(FIELD_PC_WIDTH) << std::left << " ";
        ss << " ";

        ss << std::setw(FIELD_NAME_WIDTH) << std::left << f.name;
        ss << " ";

        ss << std::setw(FIELD_OFFSET_WIDTH) << std::right << fmtBitRange(f);
        ss << " ";

        // e.g. (32)
        ss << std::right << std::setw(FIELD_SIZE_WIDTH) << fmtSize(f);
        ss << " ";

        // 0x1234
        ss << std::right <<
            std::setw(FIELD_VALUE_INT_WIDTH) << fmtHexValue(val);
        ss << " ";

        // :q
        ss << std::right << std::setw(FIELD_VALUE_STR_WIDTH) << fv.second;

        bool overlaps = fieldOverlaps(f);
        if (overlaps)
            ss << "  [FIELD OVERLAPS]";
        ss << "\n";

        // TODO: need to also emit warnings on overlapped fields
        if (std::string(UNMAPPED_FIELD_NAME) == f.name) {
            success = false;
            emitRedText(os, ss.str());
        } else if (overlaps) {
            success = false;
            emitRedText(os, ss.str());
        } else {
            os << ss.str();
        }
    }
    return success;
}

iga_status_t iga::DecodeFields(
    Platform p,
    int /* verbosity */,
    bool useNativeDecoder,
    uint32_t fmtOpts,
    std::ostream &os,
    const uint8_t *bits,
    size_t bitsLen)
{
    const Model *model = Model::LookupModel(p);
    if (model == nullptr) {
        return IGA_UNSUPPORTED_PLATFORM;
    } else if (!iga::native::IsDecodeSupported(*model, DecoderOpts())) {
        return IGA_UNSUPPORTED_PLATFORM;
    }

    decodeFieldHeaders(os);
    bool success = true;

    size_t pc = 0;
    while (pc < bitsLen) {
        if (bitsLen - pc < 4) {
            emitYellowText(os, "WARNING: extra padding at end of kernel\n");
            break;
        }
        const MInst *mi = (const MInst *)&bits[pc];
        size_t iLen = mi->isCompact() ? 8 : 16;
        if (bitsLen - pc < iLen) {
            emitYellowText(os, "WARNING: extra padding at end of kernel\n");
            break;
        }

        success &= decodeFieldsForInst(
            useNativeDecoder, fmtOpts, os, pc, *model, mi);

        pc += iLen;
    }

    return success ? IGA_SUCCESS : IGA_ERROR;
}


iga_status_t iga::DiffFields(
    Platform p,
    int verbosity,
    bool useNativeDecoder,
    uint32_t fmtOpts,
    std::ostream &os,
    const char *source1, const uint8_t *bits1, size_t bitsLen1,
    const char *source2, const uint8_t *bits2, size_t bitsLen2)
{
    return DiffFieldsFromPCs(
        p, verbosity, useNativeDecoder, fmtOpts, os,
        source1, 0, bits1, bitsLen1,
        source2, 0, bits2, bitsLen2);
}


iga_status_t iga::DiffFieldsFromPCs(
    Platform p,
    int verbosity,
    bool useNativeDecoder,
    uint32_t fmtOpts,
    std::ostream &os,
    const char *source1, size_t startPc1,
        const uint8_t *bits1, size_t bitsLen1,
    const char *source2, size_t startPc2,
        const uint8_t *bits2, size_t bitsLen2)
{
    const Model *model = Model::LookupModel(p);
    if (model == nullptr) {
        return IGA_UNSUPPORTED_PLATFORM;
    } else if (!iga::native::IsDecodeSupported(*model, DecoderOpts())) {
        return IGA_UNSUPPORTED_PLATFORM;
    }

    if (bitsLen1 == bitsLen2 &&
        memcmp(bits1, bits2, bitsLen1) == 0 &&
        verbosity < 0)
    {
        // don't show any output if they match exactly
        //
        // SPECIFY: this short-circuit prevents us from testing disassembly
        // on each instruction.  Is that what we want here?
        return IGA_SUCCESS;
    }

    if (source1 == nullptr) {
        source1 = "VALUE1";
    }
    if (source2 == nullptr) {
        source2 = "VALUE2";
    }

    os << std::setw(FIELD_PC_WIDTH) << std::left << "PC";
    os << " ";
    os << std::setw(FIELD_NAME_WIDTH) << std::left << "FIELD";
    os << " ";
    os << std::setw(FIELD_OFFSET_WIDTH) << " "; // [...:..]
    os << " ";
    os << std::setw(FIELD_SIZE_WIDTH) << " "; // (..)
    os << " ";
    os << std::setw(FIELD_VALUE_INT_WIDTH + 1 + FIELD_VALUE_STR_WIDTH) <<
        std::right << source1;
    os << " ";
    os << std::setw(FIELD_VALUE_INT_WIDTH + 1 + FIELD_VALUE_STR_WIDTH) <<
        std::right << source2;
    os << "\n";

    bool decodeFailure = false, differencesFound = false;
    size_t pc1 = startPc1, pc2 = startPc2;

    while (pc1 - startPc1 < bitsLen1 || pc2 - startPc2 < bitsLen2) {
        auto getPc = [&] (
            const char *which, const uint8_t *bits, size_t bitsLen, size_t pc)
        {
            if (bitsLen - pc < 4) {
                std::stringstream ss;
                ss << which << ": extra padding at end of kernel";
                decodeFailure = true;
            }
            const iga::MInst *mi = (const iga::MInst *)&bits[pc];
            if ((mi->isCompact() && bitsLen - pc < 8) ||
                bitsLen - pc < 16)
            {
                std::stringstream ss;
                ss << which << ": extra padding at end of kernel";
                decodeFailure = true;
            }
            return mi;
        };

        const iga::MInst
            *mi1 = getPc("kernel 1", bits1, bitsLen1, pc1 - startPc1),
            *mi2 = getPc("kernel 2", bits2, bitsLen2, pc2 - startPc2);

        // if given -q, then we ignore differences
        if (verbosity >= 0 || *mi1 != *mi2) {
            os << fmtPc(mi1, pc1) << " " <<
                disassembleInst(
                    p, useNativeDecoder, fmtOpts,
                    pc1, (const void *)mi1) << "\n";
            os << fmtPc(mi2, pc2) << " " <<
                disassembleInst(
                    p, useNativeDecoder, fmtOpts,
                    pc2, (const void *)mi2) << "\n";

            FragmentList fields1;
            bool successL = true;
            if (mi1) {
                fields1 = decodeFieldsWithWarnings(
                    *model, os, Loc((uint32_t)pc1), mi1, successL, "left");
            }
            decodeFailure |= !successL;

            bool successR = true;
            FragmentList fields2;
            if (mi2) {
                fields2 = decodeFieldsWithWarnings(
                    *model, os, Loc((uint32_t)pc2), mi2, successR, "right");
            }
            decodeFailure |= !successR;

            // union the fields
            FieldSet allFields;
            for (const auto &f : fields1) {allFields.insert(&f.first);}
            for (const auto &f : fields2) {allFields.insert(&f.first);}

            for (const Fragment *f : allFields) {
                auto inList = [&] (const FragmentList &flist) {
                    auto fieldEq = [&](const FragmentListElem &p) {
                        return p.first == *f;
                    };
                    return std::find_if(
                        flist.begin(), flist.end(), fieldEq) != flist.end();
                };
                auto findStr = [&](const FragmentList &flist) {
                    for (const auto &fp : flist) {
                        if (fp.first == *f) {
                            return std::string(fp.second);
                        }
                    }
                    return std::string("");
                };

                bool inList1 = mi1 != nullptr && inList(fields1);
                uint64_t val1 = mi1 ? mi1->getFragment(*f) : 0;
                std::string valStr1 = findStr(fields1);

                bool inList2 = mi2 != nullptr && inList(fields2);
                uint64_t val2 = mi2 ? mi2->getFragment(*f) : 0;
                std::string valStr2 = findStr(fields2);

                std::string diffToken = "  ";
                if (inList1 && inList2) {
                    if (val1 != val2) {
                        diffToken = "~~"; // changed
                        differencesFound = true;
                    } else if (verbosity < 0) {
                        // skip to the next field
                        continue;
                    } else {
                        diffToken = "  ";
                    }
                } else if (inList1) {
                    diffToken = "--"; // removed
                    differencesFound = true;
                } else if (inList2) {
                    diffToken = "++"; // added
                    differencesFound = true;
                } else {
                    // unreachable???
                    diffToken = "  ";
                }

                os << std::setw(FIELD_PC_WIDTH) << std::left << diffToken;

                // e.g. Src1.Imm32
                os << std::setw(FIELD_NAME_WIDTH) << std::left << f->name;
                os << " ";

                // e.g. [127:96]
                os << std::setw(FIELD_OFFSET_WIDTH) <<
                    std::left << fmtBitRange(*f);
                os << " ";

                // e.g. (32)
                os << std::setw(FIELD_SIZE_WIDTH) << std::left << fmtSize(*f);
                os << " ";

                // bit value
                auto fmtElem =
                    [&] (bool inList, uint64_t val, const std::string &str) {
                        if (inList) {
                            os << std::right <<
                                std::setw(FIELD_VALUE_INT_WIDTH) <<
                                fmtHex(val);
                        } else {
                            os << std::right <<
                                std::setw(FIELD_VALUE_INT_WIDTH) <<
                                " ";
                        }
                        os << " " << std::setw(FIELD_VALUE_STR_WIDTH) << str;
                    };
                if (inList1 && val1 != val2) {
                    os << Intensity::BRIGHT << Color::GREEN;
                }
                fmtElem(inList1, val1, valStr1);
                os << " ";

                if (inList2 && val1 != val2) {
                    os << Intensity::BRIGHT << Color::RED;
                }
                fmtElem(inList2, val2, valStr2);

                if (val1 != val2) {
                    os << Reset::RESET;
                }
                os << "\n";
            } // for
        } // if verbose or different

        // on to the next instruction
        if (mi1) {
            pc1 += mi1->isCompact() ? 8 : 16;
        }
        if (mi2) {
            pc2 += mi2->isCompact() ? 8 : 16;
        }
    } // for all instructions in both streams (in parallel)

    return
        decodeFailure ? IGA_DECODE_ERROR :
        differencesFound ? IGA_DIFF_FAILURE :
        IGA_SUCCESS;
}


typedef int64_t Mapping;
typedef std::pair<PC,int> PCStats;
struct MappingStats
{
//    int64_t uniqueMisses = 0;

    // the specific misses (PC,num-fields-missed)
    std::vector<PCStats> misses;

    void orderMisses() {
        std::sort(misses.begin(), misses.end(),
            [] (const PCStats &m1, const PCStats &m2) {
                return m1.second < m2.second;
            });
    }
};
struct CompactionStats
{
    using CompactionMisses = std::map<Mapping,MappingStats>;

    int64_t hits = 0;
    int64_t misses = 0;
    int64_t noCompactSet = 0;
    int64_t noCompactForm = 0; //e.g. send
    // maps field that missed to
    //   all the values that missed
    //   each value that missed maps to:
    //       - # of times it missed
    //       - # of times this miss was the unique
    std::map<const CompactionMapping *,CompactionMisses> fieldMisses;
};

static bool listInstructionCompaction(
    bool useNativeDecoder,
    uint32_t fmtOpts,
    std::ostream &os,
    CompactionStats &cmpStats,
    const Model &m,
    size_t pc,
    const MInst *bits)
{
    const int MAX_HAMMING_DIST   = 6; // what we consider a "close"  mapping
    const int MAX_CLOSE_MAPPINGS = 6;

    MInst compactedInst;
    CompactionDebugInfo cdi;
    CompactionResult cr =
        iga::native::DebugCompaction(m, bits, &compactedInst, cdi);

    bool success = true;
    switch (cr) {
    case CompactionResult::CR_SUCCESS:
        cmpStats.hits++;
        os << Color::GREEN << Intensity::BRIGHT <<
            "=> compaction hit " << Reset::RESET << "\n";
        success &=
            decodeFieldsForInst(useNativeDecoder, fmtOpts, os, pc, m, &compactedInst);
        break;
    case CompactionResult::CR_MISS: {
        cmpStats.misses++;
        os << Color::YELLOW << Intensity::BRIGHT <<
            "=> compaction miss " << Reset::RESET << "\n";

        bool missesImm = false;
        for (const CompactionMapping *cf : cdi.fieldMisses) {
            missesImm |= cf->isSrcImmField();
        }

        for (size_t i = 0, len = cdi.fieldMisses.size(); i < len; i++) {
            Op op = cdi.fieldOps[i];
            const CompactionMapping &cf = *cdi.fieldMisses[i];
            uint64_t missedMapping = cdi.fieldMapping[i];

            auto &mms = cmpStats.fieldMisses[&cf];
            MappingStats &ms = mms[missedMapping];
            ms.misses.push_back(PCStats((PC)pc,(int)cdi.fieldMisses.size()));

            // emit the field that missed
            os << Color::WHITE << Intensity::BRIGHT <<
                cf.index.name << Reset::RESET;
            os << ": ";
            for (int mIx = 0; mIx < (int)cf.numMappings; ++mIx) {
                if (mIx != 0) {
                    os << "`";
                }
                const Field *mf = cf.mappings[mIx];
                os << mf->name;
            }
            os << " misses compaction table\n";

            // compute the total number of bits in mappings
            // and the number of hex digits needed to for output alignment
            int hexDigits = ((int)cf.countNumBitsMapped() + 4 - 1)/4; // align up

            // emit the actual mapping we were looking for
            os << "       " << fmtHex(missedMapping, hexDigits);
            if (cf.mappings == nullptr) { // e.g. src1imm12, skip it
                os << "   [no explicit mappings] \n";
                continue;
            }
            os << ": ";
            cf.emitBinary(os, missedMapping);
            os << ": (";
            if (cf.format) {
                os << cf.format(op, missedMapping);
            }
            os << ") is the necessary table entry\n";

            // find the closest hits
            std::vector<size_t> closestIndices;
            for (size_t dist = 1;
                dist < MAX_HAMMING_DIST &&
                    closestIndices.size() < MAX_CLOSE_MAPPINGS;
                dist++)
            {
                // closest hits of distance `dist`
                for (size_t mIx = 0; mIx < cf.numValues; mIx++) {
                    uint64_t val = cf.values[mIx];
                    std::bitset<64> bs(val ^ missedMapping);
                    if (bs.count() == dist) {
                        closestIndices.push_back(mIx);
                    }
                }
            } // for dist=1...8 or until we find nearest K

              // emit information on the closest mappings
            if (closestIndices.empty()) {
                os << "  no close mappings found\n";
            } else {
                os << "  closest mappings are:\n";
                for (auto cIx : closestIndices) {
                    uint64_t closeMapping = cf.values[cIx];
                    std::bitset<64> bs(closeMapping ^ missedMapping);
                    os << "  #" << std::setw(2) << std::left << cIx;
                    os << "  " << fmtHex(cf.values[cIx], hexDigits) << ": ";
                    int bitOff = (int)cf.countNumBitsMapped();
                    // walk through the fields listing who misses
                    // (from high to low bits)
                    std::vector<const Field *> missingFields;
                    for (int mIx = 0; mIx < (int)cf.numMappings; ++mIx) {
                        if (mIx != 0) {
                            os << "`";
                        }
                        const Field *mf = cf.mappings[mIx];
                        bitOff -= mf->length();
                        auto missedVal =
                            iga::getBits(missedMapping, bitOff, mf->length());
                        auto closeVal =
                            iga::getBits(closeMapping, bitOff, mf->length());
                        if (missedVal != closeVal) {
                            os << Color::RED << Intensity::BRIGHT;
                            fmtBinaryDigits(os, closeVal, mf->length());
                            os << Reset::RESET;
                            missingFields.push_back(mf);
                        } else {
                            fmtBinaryDigits(os, closeVal, mf->length());
                        }
                    }
                    os << ": ";

                    commafyList(os, missingFields,
                        [] (std::ostream &os, const Field *f)
                    {
                        os << Color::RED << Intensity::BRIGHT;
                        os << f->name;
                        os << Reset::RESET;
                    });
                    if (missingFields.size() == 1) {
                        os << " misses";
                    } else {
                        os << " miss";
                    }
                    os << "\n";
                }
            }
        }
        break;
    }
    case CompactionResult::CR_NO_COMPACT:
        cmpStats.noCompactSet++;
        os << Color::WHITE << "{NoCompact/Uncompacted} set" <<
            Reset::RESET << "\n";
        break;
    case CompactionResult::CR_NO_FORMAT:
        cmpStats.noCompactForm++;
        emitRedText(os, "=> no compacted form\n");
        break;
    default:
        os << "INTERNAL ERROR: " << (int)cr << "\n";
        success = false;
    }
    return success;
}

iga_status_t iga::DebugCompaction(
    Platform p,
    int /* verbosity */,
    bool useNativeDecoder,
    uint32_t fmtOpts,
    std::ostream &os,
    const uint8_t *bits,
    size_t bitsLen)
{
    const Model *model = Model::LookupModel(p);
    if (model == nullptr) {
        return IGA_UNSUPPORTED_PLATFORM;
    } else if (!iga::native::IsDecodeSupported(*model, DecoderOpts())) {
        return IGA_UNSUPPORTED_PLATFORM;
    }

    CompactionStats cs;

    bool success = true;
    size_t pc = 0;
    while (pc < bitsLen) {
        if (bitsLen - pc < 4) {
            emitYellowText(os, "WARNING: extra padding at end of kernel\n");
            break;
        }
        const MInst *mi = (const MInst *)&bits[pc];
        size_t iLen = mi->isCompact() ? 8 : 16;
        if (bitsLen - pc < iLen) {
            emitYellowText(os, "WARNING: extra padding at end of kernel\n");
            break;
        }

        os << "============================================================\n";
        auto syntax =
            disassembleInst(p, useNativeDecoder, fmtOpts, pc, (const void *)mi);
        os << fmtPc(mi, pc) << " " <<  syntax << "\n";
        os.flush();
        success &= listInstructionCompaction(
            useNativeDecoder, fmtOpts, os, cs, *model, pc, mi);
        pc += iLen;
    }
    os << "\n";
    os << "\n";
    os << "*************************** SUMMARY ***************************\n";
    uint64_t totalInsts =
        cs.hits + cs.misses + cs.noCompactSet + cs.noCompactForm;
    auto emitSubset = [&](const char *name, int64_t val) {
        os << "  ";
        std::string title = name;
        title += ':';
        os << std::setw(24) << std::setfill(' ') << std::left << title;
        os << std::setw(12) << std::right << val;
        os << "  ";
        double pct = 100.0*val/(double)totalInsts;
        os << std::setprecision(3) << std::setw(8) << std::right << pct << "%";
        os << "\n";
    };
    emitSubset("hits", cs.hits);
    emitSubset("misses", cs.misses);
    emitSubset("{Uncompacted} set", cs.noCompactSet);
    emitSubset("no compact form", cs.noCompactForm);
    os << std::setw(24) << "TOTAL:" <<
        std::setw(10) << std::right << totalInsts << "\n";
    os << "***************************************************************\n";
    // show the various indices that missed
    int64_t totalMisses = 0;
    for (const auto &missEntry : cs.fieldMisses) {
        const CompactionMapping *cf = missEntry.first;
        os << std::setw(24) << cf->index.name;
        int64_t misses = 0;
        for (const auto ms : missEntry.second) {
            misses += ms.second.misses.size();
        }
        totalMisses += misses;

        os << "  ";
        os << std::setw(10) << std::right << misses;
        os << "\n";
    }

    // now list the actual misses
    os << "***************************************************************\n";
    for (const auto &missEntry : cs.fieldMisses) {
        const CompactionMapping *cf = missEntry.first;
        os << cf->index.name << "\n";

        // order the misses by the total number
        std::vector<std::pair<Mapping,MappingStats>> orderedEntries;
        for (const auto ms : missEntry.second) {
            orderedEntries.push_back(ms);
        }
        std::sort(
            orderedEntries.begin(),
            orderedEntries.end(),
            [] (const std::pair<Mapping,MappingStats> &p1,
                const std::pair<Mapping,MappingStats> &p2)
            {
                return p1.second.misses.size() > p2.second.misses.size();
            });

        for (const auto &ms : orderedEntries) {
            Mapping mVal = ms.first;
            const MappingStats &mStats = ms.second;

            os << "    ";
            cf->emitBinary(os, mVal); // e.g. 000`0010`0`00
            os << "  total misses:" << mStats.misses.size();
            size_t misses1 = 0, misses2 = 0, misses3plus = 0;
            for (const auto &missExample : mStats.misses) {
                if (missExample.second == 1) {
                    misses1++;
                } else if (missExample.second == 1) {
                    misses2++;
                } else if (missExample.second == 2) {
                    misses2++;
                } else {
                    misses3plus++;
                }
            }
            os << "  1m:" << misses1 <<
                "  2m:" << misses2 <<
                "  3+m:" << misses3plus;

            if (cf->format) {
                os << "  ";
                Op HACK = Op::ADD; // gives - instead of ~ for bitwise,
                                   // but it's the best I can do
                os << cf->format(HACK, mVal);
            }
            os << "\n";

            // emit those that miss
            // mStats.orderMisses();
            for (const auto &missExample : mStats.misses) {
                PC missPc = missExample.first;
                int totalMissesForThisPc = missExample.second;
                os << "        misses " << totalMissesForThisPc << ": " <<
                    disassembleInst(
                        p, useNativeDecoder,
                        fmtOpts, missPc, bits + missPc) << "\n";
            }
        }
    }

    return IGA_SUCCESS;
}