/***************************************************************************************************

  Zyan Disassembler Library (Zydis)

  Original Author : Florian Bernd

 * 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.

***************************************************************************************************/

#include "config.h"

#if ENABLE(ZYDIS)

#include "ZydisRegister.h"

/* ============================================================================================== */
/* Register strings                                                                               */
/* ============================================================================================== */

#include "ZydisGeneratedEnumRegister.inc"

/* ============================================================================================== */
/* Register-class mapping                                                                         */
/* ============================================================================================== */

/**
 * Defines the `ZydisRegisterMapItem` struct.
 */
typedef struct ZydisRegisterMapItem_
{
    /**
     * The register class.
     */
    ZydisRegisterClass class;
    /**
     * The lowest register of the current class.
     */
    ZydisRegister lo;
    /**
     * The highest register of the current class.
     */
    ZydisRegister hi;
    /**
     * The width of registers of the current class in 16- and 32-bit mode.
     */
    ZydisRegisterWidth width;
    /**
     * The width of registers of the current class in 64-bit mode.
     */
    ZydisRegisterWidth width64;
} ZydisRegisterMapItem;

/**
 * Provides register to register-class and register-class + id to register mappings.
 */
static const ZydisRegisterMapItem REGISTER_MAP[] =
{
    { ZYDIS_REGCLASS_INVALID  , ZYDIS_REGISTER_NONE   , ZYDIS_REGISTER_NONE   ,    0   ,    0 },
    { ZYDIS_REGCLASS_GPR8     , ZYDIS_REGISTER_AL     , ZYDIS_REGISTER_R15B   ,    8   ,    8 },
    { ZYDIS_REGCLASS_GPR16    , ZYDIS_REGISTER_AX     , ZYDIS_REGISTER_R15W   ,   16   ,   16 },
    { ZYDIS_REGCLASS_GPR32    , ZYDIS_REGISTER_EAX    , ZYDIS_REGISTER_R15D   ,   32   ,   32 },
    { ZYDIS_REGCLASS_GPR64    , ZYDIS_REGISTER_RAX    , ZYDIS_REGISTER_R15    ,    0   ,   64 },
    { ZYDIS_REGCLASS_X87      , ZYDIS_REGISTER_ST0    , ZYDIS_REGISTER_ST7    ,   80   ,   80 },
    { ZYDIS_REGCLASS_MMX      , ZYDIS_REGISTER_MM0    , ZYDIS_REGISTER_MM7    ,   64   ,   64 },
    { ZYDIS_REGCLASS_XMM      , ZYDIS_REGISTER_XMM0   , ZYDIS_REGISTER_XMM31  ,  128   ,  128 },
    { ZYDIS_REGCLASS_YMM      , ZYDIS_REGISTER_YMM0   , ZYDIS_REGISTER_YMM31  ,  256   ,  256 },
    { ZYDIS_REGCLASS_ZMM      , ZYDIS_REGISTER_ZMM0   , ZYDIS_REGISTER_ZMM31  ,  512   ,  512 },
    { ZYDIS_REGCLASS_TMM      , ZYDIS_REGISTER_TMM0   , ZYDIS_REGISTER_TMM7   , 8192   , 8192 },
    { ZYDIS_REGCLASS_FLAGS    , ZYDIS_REGISTER_FLAGS  , ZYDIS_REGISTER_RFLAGS ,    0   ,    0 },
    { ZYDIS_REGCLASS_IP       , ZYDIS_REGISTER_IP     , ZYDIS_REGISTER_RIP    ,    0   ,    0 },
    { ZYDIS_REGCLASS_SEGMENT  , ZYDIS_REGISTER_ES     , ZYDIS_REGISTER_GS     ,   16   ,   16 },
    { ZYDIS_REGCLASS_TEST     , ZYDIS_REGISTER_TR0    , ZYDIS_REGISTER_TR7    ,   32   ,   32 },
    { ZYDIS_REGCLASS_CONTROL  , ZYDIS_REGISTER_CR0    , ZYDIS_REGISTER_CR15   ,   32   ,   64 },
    { ZYDIS_REGCLASS_DEBUG    , ZYDIS_REGISTER_DR0    , ZYDIS_REGISTER_DR15   ,   32   ,   64 },
    { ZYDIS_REGCLASS_MASK     , ZYDIS_REGISTER_K0     , ZYDIS_REGISTER_K7     ,    0   ,    0 },
    { ZYDIS_REGCLASS_BOUND    , ZYDIS_REGISTER_BND0   , ZYDIS_REGISTER_BND3   ,  128   ,  128 }
};

/* ============================================================================================== */
/* Exported functions                                                                             */
/* ============================================================================================== */

/* ---------------------------------------------------------------------------------------------- */
/* Register                                                                                       */
/* ---------------------------------------------------------------------------------------------- */

ZydisRegister ZydisRegisterEncode(ZydisRegisterClass register_class, ZyanU8 id)
{
    switch (register_class)
    {
    case ZYDIS_REGCLASS_INVALID:
    case ZYDIS_REGCLASS_FLAGS:
    case ZYDIS_REGCLASS_IP:
        break;
    default:
        if (((ZyanUSize)register_class < ZYAN_ARRAY_LENGTH(REGISTER_MAP)) &&
            (id <= (REGISTER_MAP[register_class].hi - REGISTER_MAP[register_class].lo)))
        {
            return REGISTER_MAP[register_class].lo + id;
        }
    }
    return ZYDIS_REGISTER_NONE;
}

ZyanI8 ZydisRegisterGetId(ZydisRegister reg)
{
    for (ZyanUSize i = 0; i < ZYAN_ARRAY_LENGTH(REGISTER_MAP); ++i)
    {
        switch (REGISTER_MAP[i].class)
        {
        case ZYDIS_REGCLASS_INVALID:
        case ZYDIS_REGCLASS_FLAGS:
        case ZYDIS_REGCLASS_IP:
            break;
        default:
            if ((reg >= REGISTER_MAP[i].lo) && (reg <= REGISTER_MAP[i].hi))
            {
                return (ZyanU8)(reg - REGISTER_MAP[i].lo);
            }
        }
    }
    return -1;
}

ZydisRegisterClass ZydisRegisterGetClass(ZydisRegister reg)
{
    for (ZyanUSize i = 0; i < ZYAN_ARRAY_LENGTH(REGISTER_MAP); ++i)
    {
        if ((reg >= REGISTER_MAP[i].lo) && (reg <= REGISTER_MAP[i].hi))
        {
            return REGISTER_MAP[i].class;
        }
    }
    return ZYDIS_REGCLASS_INVALID;
}

ZydisRegisterWidth ZydisRegisterGetWidth(ZydisMachineMode mode, ZydisRegister reg)
{
    // Special cases
    switch (reg)
    {
    case ZYDIS_REGISTER_X87CONTROL:
    case ZYDIS_REGISTER_X87STATUS:
    case ZYDIS_REGISTER_X87TAG:
        return 16;
    case ZYDIS_REGISTER_IP:
    case ZYDIS_REGISTER_FLAGS:
        return 16;
    case ZYDIS_REGISTER_EIP:
    case ZYDIS_REGISTER_EFLAGS:
        return 32;
    case ZYDIS_REGISTER_RIP:
    case ZYDIS_REGISTER_RFLAGS:
        return (mode == ZYDIS_MACHINE_MODE_LONG_64) ? 64 : 0;
    case ZYDIS_REGISTER_BNDCFG:
    case ZYDIS_REGISTER_BNDSTATUS:
        return 64;
    case ZYDIS_REGISTER_XCR0:
        return 64;
    case ZYDIS_REGISTER_PKRU:
    case ZYDIS_REGISTER_MXCSR:
        return 32;
    default:
        break;
    }

    // Register classes
    for (ZyanUSize i = 0; i < ZYAN_ARRAY_LENGTH(REGISTER_MAP); ++i)
    {
        if ((reg >= REGISTER_MAP[i].lo) && (reg <= REGISTER_MAP[i].hi))
        {
            return (mode == ZYDIS_MACHINE_MODE_LONG_64) ?
                REGISTER_MAP[i].width64 : REGISTER_MAP[i].width;
        }
    }
    return 0;
}

ZydisRegister ZydisRegisterGetLargestEnclosing(ZydisMachineMode mode, ZydisRegister reg)
{
    static const ZyanU8 GPR8_MAPPING[20] =
    {
        /* AL   */  0,
        /* CL   */  1,
        /* DL   */  2,
        /* BL   */  3,
        /* AH   */  0,
        /* CH   */  1,
        /* DH   */  2,
        /* BH   */  3,
        /* SPL  */  4,
        /* BPL  */  5,
        /* SIL  */  6,
        /* DIL  */  7,
        /* R8B  */  8,
        /* R9B  */  9,
        /* R10B */ 10,
        /* R11B */ 11,
        /* R12B */ 12,
        /* R13B */ 13,
        /* R14B */ 14,
        /* R15B */ 15,
    };

    for (ZyanUSize i = 0; i < ZYAN_ARRAY_LENGTH(REGISTER_MAP); ++i)
    {
        if ((reg >= REGISTER_MAP[i].lo) && (reg <= REGISTER_MAP[i].hi))
        {
            const ZydisRegisterClass reg_class = REGISTER_MAP[i].class;
            if ((reg_class == ZYDIS_REGCLASS_GPR64) && (mode != ZYDIS_MACHINE_MODE_LONG_64))
            {
                return ZYDIS_REGISTER_NONE;
            }

            ZyanU8 reg_id = (ZyanU8)(reg - REGISTER_MAP[reg_class].lo);
            switch (reg_class)
            {
            case ZYDIS_REGCLASS_GPR8:
                reg_id = GPR8_MAPPING[reg_id];
                ZYAN_FALLTHROUGH;
            case ZYDIS_REGCLASS_GPR16:
            case ZYDIS_REGCLASS_GPR32:
            case ZYDIS_REGCLASS_GPR64:
                switch (mode)
                {
                case ZYDIS_MACHINE_MODE_LONG_64:
                    return REGISTER_MAP[ZYDIS_REGCLASS_GPR64].lo + reg_id;
                case ZYDIS_MACHINE_MODE_LONG_COMPAT_32:
                case ZYDIS_MACHINE_MODE_LEGACY_32:
                    return REGISTER_MAP[ZYDIS_REGCLASS_GPR32].lo + reg_id;
                case ZYDIS_MACHINE_MODE_LONG_COMPAT_16:
                case ZYDIS_MACHINE_MODE_LEGACY_16:
                case ZYDIS_MACHINE_MODE_REAL_16:
                    return REGISTER_MAP[ZYDIS_REGCLASS_GPR16].lo + reg_id;
                default:
                    return ZYDIS_REGISTER_NONE;
                }
            case ZYDIS_REGCLASS_XMM:
            case ZYDIS_REGCLASS_YMM:
            case ZYDIS_REGCLASS_ZMM:
#if defined(ZYDIS_DISABLE_AVX512) && defined(ZYDIS_DISABLE_KNC)
                return REGISTER_MAP[ZYDIS_REGCLASS_YMM].lo + reg_id;
#else
                return REGISTER_MAP[ZYDIS_REGCLASS_ZMM].lo + reg_id;
#endif
            default:
                return ZYDIS_REGISTER_NONE;
            }
        }
    }

    return ZYDIS_REGISTER_NONE;
}

const char* ZydisRegisterGetString(ZydisRegister reg)
{
    if ((ZyanUSize)reg >= ZYAN_ARRAY_LENGTH(STR_REGISTER))
    {
        return ZYAN_NULL;
    }
    return STR_REGISTER[reg].data;
}

const ZydisShortString* ZydisRegisterGetStringWrapped(ZydisRegister reg)
{
    if ((ZyanUSize)reg >= ZYAN_ARRAY_LENGTH(STR_REGISTER))
    {
        return ZYAN_NULL;
    }
    return &STR_REGISTER[reg];
}

/* ---------------------------------------------------------------------------------------------- */
/* Register class                                                                                 */
/* ---------------------------------------------------------------------------------------------- */

ZydisRegisterWidth ZydisRegisterClassGetWidth(ZydisMachineMode mode,
    ZydisRegisterClass register_class)
{
    if ((ZyanUSize)register_class < ZYAN_ARRAY_LENGTH(REGISTER_MAP))
    {
        return (mode == ZYDIS_MACHINE_MODE_LONG_64) ?
            REGISTER_MAP[register_class].width64 : REGISTER_MAP[register_class].width;
    }
    return 0;
}

/* ---------------------------------------------------------------------------------------------- */

/* ============================================================================================== */

#endif /* ENABLE(ZYDIS) */
