Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/js/src/zydis/Zydis/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 154 kB image not shown  

Quelle  Encoder.c

  Sprache: C
 

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

  Zyan Disassembler Library (Zydis)

  Original Author : Mappa

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

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


// ReSharper disable CppClangTidyClangDiagnosticSwitchEnum
// ReSharper disable CppClangTidyClangDiagnosticCoveredSwitchDefault
// ReSharper disable CppClangTidyClangDiagnosticImplicitFallthrough

#include "zydis/Zycore/LibC.h"
#include "zydis/Zydis/Encoder.h"
#include "zydis/Zydis/Utils.h"
#include "zydis/Zydis/Internal/EncoderData.h"
#include "zydis/Zydis/Internal/SharedData.h"

/* ============================================================================================== */
/* Macros                                                                                         */
/* ============================================================================================== */

/* ---------------------------------------------------------------------------------------------- */
/* Constants                                                                                      */
/* ---------------------------------------------------------------------------------------------- */

#define ZYDIS_OPSIZE_MAP_BYTEOP                 1
#define ZYDIS_OPSIZE_MAP_DEFAULT64              4
#define ZYDIS_OPSIZE_MAP_FORCE64                5
#define ZYDIS_ADSIZE_MAP_IGNORED                1
#define ZYDIS_LEGACY_SEGMENTS                   (ZYDIS_ATTRIB_HAS_SEGMENT_CS | \
                                                 ZYDIS_ATTRIB_HAS_SEGMENT_SS | \
                                                 ZYDIS_ATTRIB_HAS_SEGMENT_DS | \
                                                 ZYDIS_ATTRIB_HAS_SEGMENT_ES)
#define ZYDIS_ENCODABLE_PREFIXES_NO_SEGMENTS    (ZYDIS_ENCODABLE_PREFIXES ^ \
                                                 ZYDIS_ATTRIB_HAS_SEGMENT)

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

/* ============================================================================================== */
/* Internal enums and types                                                                       */
/* ============================================================================================== */

/**
 * Usage of `REX.W` prefix makes it impossible to use some byte-sized registers. Values of this
 * enum are used to track and facilitate enforcement of these restrictions.
 */

typedef enum ZydisEncoderRexType_
{
    ZYDIS_REX_TYPE_UNKNOWN,
    ZYDIS_REX_TYPE_REQUIRED,
    ZYDIS_REX_TYPE_FORBIDDEN,

    /**
     * Maximum value of this enum.
     */

    ZYDIS_REX_TYPE_MAX_VALUE = ZYDIS_REX_TYPE_FORBIDDEN,
    /**
     * The minimum number of bits required to represent all values of this enum.
     */

    ZYDIS_REX_TYPE_REQUIRED_BITS = ZYAN_BITS_TO_REPRESENT(ZYDIS_REX_TYPE_MAX_VALUE)
} ZydisEncoderRexType;

/**
 * Primary structure used during instruction matching phase. Once filled it contains information
 * about matched instruction definition and some values deduced from encoder request. It gets
 * converted to `ZydisEncoderInstruction` during instruction building phase.
 */

typedef struct ZydisEncoderInstructionMatch_
{
    /**
     * A pointer to the `ZydisEncoderRequest` instance.
     */

    const ZydisEncoderRequest *request;
    /**
     * A pointer to the `ZydisEncodableInstruction` instance.
     */

    const ZydisEncodableInstruction *definition;
    /**
     * A pointer to the `ZydisInstructionDefinition` instance.
     */

    const ZydisInstructionDefinition *base_definition;
    /**
     * A pointer to the `ZydisOperandDefinition` array.
     */

    const ZydisOperandDefinition *operands;
    /**
     * Encodable attributes for this instruction.
     */

    ZydisInstructionAttributes attributes;
    /**
     * Effective operand size attribute.
     */

    ZyanU8 eosz;
    /**
     * Effective address size attribute.
     */

    ZyanU8 easz;
    /**
     * Effective displacement size.
     */

    ZyanU8 disp_size;
    /**
     * Effective immediate size.
     */

    ZyanU8 imm_size;
    /**
     * Exponent of compressed displacement scale factor (2^cd8_scale)
     */

    ZyanU8 cd8_scale;
    /**
     * `REX` prefix constraints.
     */

    ZydisEncoderRexType rex_type;
    /**
     * True for special cases where operand size attribute must be lower than 64 bits.
     */

    ZyanBool eosz64_forbidden;
    /**
     * True when instruction definition has relative operand (used for branching instructions).
     */

    ZyanBool has_rel_operand;
} ZydisEncoderInstructionMatch;

/**
 * Encapsulates information about writable buffer.
 */

typedef struct ZydisEncoderBuffer_
{
    /**
     * A pointer to actual data buffer.
     */

    ZyanU8 *buffer;
    /**
     * Size of this buffer.
     */

    ZyanUSize size;
    /**
     * Current write offset.
     */

    ZyanUSize offset;
} ZydisEncoderBuffer;

/**
 * Low-level instruction representation. Once filled this structure contains all information
 * required for final instruction emission phase.
 */

typedef struct ZydisEncoderInstruction_
{
    /**
     * Encodable attributes for this instruction.
     */

    ZydisInstructionAttributes attributes;
    /**
     * The instruction encoding.
     */

    ZydisInstructionEncoding encoding;
    /**
     * The opcode map.
     */

    ZydisOpcodeMap opcode_map;
    /**
     * The opcode.
     */

    ZyanU8 opcode;
    /**
     * The `vvvv` field (`VEX`, `EVEX`, `MVEX`, `XOP`).
     */

    ZyanU8 vvvv;
    /**
     * The `sss` field (`MVEX`).
     */

    ZyanU8 sss;
    /**
     * The mask register ID.
     */

    ZyanU8 mask;
    /**
     * The vector length.
     */

    ZyanU8 vector_length;
    /**
     * The `mod` component of Mod/RM byte.
     */

    ZyanU8 mod;
    /**
     * The `reg` component of Mod/RM byte.
     */

    ZyanU8 reg;
    /**
     * The `rm` component of Mod/RM byte.
     */

    ZyanU8 rm;
    /**
     * The scale component of SIB byte.
     */

    ZyanU8 scale;
    /**
     * The index component of SIB byte.
     */

    ZyanU8 index;
    /**
     * The base component of SIB byte.
     */

    ZyanU8 base;
    /**
     * The `REX.W` bit.
     */

    ZyanBool rex_w;
    /**
     * True if using zeroing mask (`EVEX`).
     */

    ZyanBool zeroing;
    /**
     * True if using eviction hint (`MVEX`).
     */

    ZyanBool eviction_hint;
    /**
     * Size of displacement value.
     */

    ZyanU8 disp_size;
    /**
     * Size of immediate value.
     */

    ZyanU8 imm_size;
    /**
     * The displacement value.
     */

    ZyanU64 disp;
    /**
     * The immediate value.
     */

    ZyanU64 imm;
} ZydisEncoderInstruction;

/* ============================================================================================== */
/* Internal functions                                                                             */
/* ============================================================================================== */

/**
 * Converts `ZydisInstructionEncoding` to `ZydisEncodableEncoding`.
 *
 * @param   encoding `ZydisInstructionEncoding` value to convert.
 *
 * @return  Equivalent `ZydisEncodableEncoding` value.
 */

static ZydisEncodableEncoding ZydisGetEncodableEncoding(ZydisInstructionEncoding encoding)
{
    static const ZydisEncodableEncoding encoding_lookup[6] =
    {
        ZYDIS_ENCODABLE_ENCODING_LEGACY,
        ZYDIS_ENCODABLE_ENCODING_3DNOW,
        ZYDIS_ENCODABLE_ENCODING_XOP,
        ZYDIS_ENCODABLE_ENCODING_VEX,
        ZYDIS_ENCODABLE_ENCODING_EVEX,
        ZYDIS_ENCODABLE_ENCODING_MVEX,
    };
    ZYAN_ASSERT((ZyanUSize)encoding <= ZYDIS_INSTRUCTION_ENCODING_MAX_VALUE);
    return encoding_lookup[encoding];
}

/**
 * Converts `ZydisMachineMode` to default stack width value expressed in bits.
 *
 * @param   machine_mode `ZydisMachineMode` value to convert.
 *
 * @return  Stack width for requested machine mode.
 */

static ZyanU8 ZydisGetMachineModeWidth(ZydisMachineMode machine_mode)
{
    ZYAN_ASSERT((ZyanUSize)machine_mode <= ZYDIS_MACHINE_MODE_MAX_VALUE);
    static const ZyanU8 lookup[6] =
    {
        /* ZYDIS_MACHINE_MODE_LONG_64 */            64,
        /* ZYDIS_MACHINE_MODE_LONG_COMPAT_32 */     32,
        /* ZYDIS_MACHINE_MODE_LONG_COMPAT_16 */     16,
        /* ZYDIS_MACHINE_MODE_LEGACY_32 */          32,
        /* ZYDIS_MACHINE_MODE_LEGACY_16 */          16,
        /* ZYDIS_MACHINE_MODE_REAL_16 */            16,
    };
    return lookup[machine_mode];
}

/**
 * Converts `ZydisAddressSizeHint` to address size expressed in bits.
 *
 * @param   hint Address size hint.
 *
 * @return  Address size in bits.
 */

static ZyanU8 ZydisGetAszFromHint(ZydisAddressSizeHint hint)
{
    ZYAN_ASSERT((ZyanUSize)hint <= ZYDIS_ADDRESS_SIZE_HINT_MAX_VALUE);
    static const ZyanU8 lookup[ZYDIS_ADDRESS_SIZE_HINT_MAX_VALUE + 1] = { 0, 16, 32, 64 };
    return lookup[hint];
}

/**
 * Converts `ZydisOperandSizeHint` to operand size expressed in bits.
 *
 * @param   hint Operand size hint.
 *
 * @return  Operand size in bits.
 */

static ZyanU8 ZydisGetOszFromHint(ZydisOperandSizeHint hint)
{
    ZYAN_ASSERT((ZyanUSize)hint <= ZYDIS_OPERAND_SIZE_HINT_MAX_VALUE);
    static const ZyanU8 lookup[ZYDIS_OPERAND_SIZE_HINT_MAX_VALUE + 1] = { 0, 8, 16, 32, 64 };
    return lookup[hint];
}

/**
 * Calculates maximum size of absolute address value based on address size hint.
 *
 * @param   request A pointer to `ZydisEncoderRequest` struct.
 *
 * @return  Maximum address size in bits.
 */

static ZyanU8 ZydisGetMaxAddressSize(const ZydisEncoderRequest *request)
{
    ZyanU8 addr_size = ZydisGetAszFromHint(request->address_size_hint);
    if (addr_size == 0)
    {
        addr_size = ZydisGetMachineModeWidth(request->machine_mode);
    }
    return addr_size;
}

/**
 * Calculates effective operand size.
 *
 * @param   match            A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   size_table       Array of possible size values for different operand sizes.
 * @param   desired_size     Operand size requested by caller.
 * @param   exact_match_mode True if desired_size must be matched exactly, false when
 *                           "not lower than" matching is desired.
 *
 * @return  Effective operand size in bits.
 */

static ZyanU8 ZydisGetOperandSizeFromElementSize(ZydisEncoderInstructionMatch *match,
    const ZyanU16 *size_table, ZyanU16 desired_size, ZyanBool exact_match_mode)
{
    if ((match->base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_DEFAULT64) &&
        (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64))
    {
        if ((exact_match_mode && (size_table[2] == desired_size)) ||
            (!exact_match_mode && (size_table[2] >= desired_size)))
        {
            return 64;
        }
        else if (size_table[0] == desired_size)
        {
            return 16;
        }
    }
    else if ((match->base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_FORCE64) &&
             (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64))
    {
        if (size_table[2] == desired_size)
        {
            return 64;
        }
    }
    else
    {
        static const ZyanI8 eosz_priority_lookup[4][3] =
        {
            {  0,  1, -1 },
            {  1,  0, -1 },
            {  1,  2,  0 },
        };
        const ZyanU8 eosz_index = ZydisGetMachineModeWidth(match->request->machine_mode) >> 5;
        for (int i = 0; i < 3; ++i)
        {
            const ZyanI8 eosz_candidate = eosz_priority_lookup[eosz_index][i];
            if ((eosz_candidate == -1) ||
                !(match->definition->operand_sizes & (1 << eosz_candidate)))
            {
                continue;
            }
            if ((exact_match_mode && (size_table[eosz_candidate] == desired_size)) ||
                (!exact_match_mode && (size_table[eosz_candidate] >= desired_size)))
            {
                return 16 << eosz_candidate;
            }
        }
    }

    return 0;
}

/**
 * Calculates effective immediate size.
 *
 * @param   match        A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   size_table   Array of possible size values for different operand sizes.
 * @param   min_imm_size Minimum immediate size.
 *
 * @return  Effective operand size in bits.
 */

static ZyanU8 ZydisGetScaledImmSize(ZydisEncoderInstructionMatch *match, const ZyanU16 *size_table,
    ZyanU8 min_imm_size)
{
    if (match->eosz == 0)
    {
        match->eosz = ZydisGetOperandSizeFromElementSize(match, size_table, min_imm_size,
            ZYAN_FALSE);
        return match->eosz != 0 ? (ZyanU8)size_table[match->eosz >> 5] : 0;
    }

    const ZyanU8 index = match->eosz >> 5;
    return size_table[index] >= min_imm_size ? (ZyanU8)size_table[index] : 0;
}

/**
 * Calculates size of smallest integral type able to represent provided signed value.
 *
 * @param   imm Immediate to be represented.
 *
 * @return  Size of smallest integral type able to represent provided signed value.
 */

static ZyanU8 ZydisGetSignedImmSize(ZyanI64 imm)
{
    if (imm >= ZYAN_INT8_MIN && imm <= ZYAN_INT8_MAX)
    {
        return 8;
    }
    if (imm >= ZYAN_INT16_MIN && imm <= ZYAN_INT16_MAX)
    {
        return 16;
    }
    if (imm >= ZYAN_INT32_MIN && imm <= ZYAN_INT32_MAX)
    {
        return 32;
    }

    return 64;
}

/**
 * Calculates size of smallest integral type able to represent provided unsigned value.
 *
 * @param   imm Immediate to be represented.
 *
 * @return  Size of smallest integral type able to represent provided unsigned value.
 */

static ZyanU8 ZydisGetUnsignedImmSize(ZyanU64 imm)
{
    if (imm <= ZYAN_UINT8_MAX)
    {
        return 8;
    }
    if (imm <= ZYAN_UINT16_MAX)
    {
        return 16;
    }
    if (imm <= ZYAN_UINT32_MAX)
    {
        return 32;
    }

    return 64;
}

/**
 * Checks if operand encoding encodes a signed immediate value.
 *
 * @param   encoding Operand encoding for immediate value.
 *
 * @return  True for encodings that represent signed values, false otherwise.
 */

static ZyanBool ZydisIsImmSigned(ZydisOperandEncoding encoding)
{
    switch (encoding)
    {
    case ZYDIS_OPERAND_ENCODING_SIMM8:
    case ZYDIS_OPERAND_ENCODING_SIMM16:
    case ZYDIS_OPERAND_ENCODING_SIMM32:
    case ZYDIS_OPERAND_ENCODING_SIMM64:
    case ZYDIS_OPERAND_ENCODING_SIMM16_32_64:
    case ZYDIS_OPERAND_ENCODING_SIMM32_32_64:
    case ZYDIS_OPERAND_ENCODING_SIMM16_32_32:
    case ZYDIS_OPERAND_ENCODING_JIMM8:
    case ZYDIS_OPERAND_ENCODING_JIMM16:
    case ZYDIS_OPERAND_ENCODING_JIMM32:
    case ZYDIS_OPERAND_ENCODING_JIMM64:
    case ZYDIS_OPERAND_ENCODING_JIMM16_32_64:
    case ZYDIS_OPERAND_ENCODING_JIMM32_32_64:
    case ZYDIS_OPERAND_ENCODING_JIMM16_32_32:
    case ZYDIS_OPERAND_ENCODING_DISP8:
    case ZYDIS_OPERAND_ENCODING_DISP16:
    case ZYDIS_OPERAND_ENCODING_DISP32:
    case ZYDIS_OPERAND_ENCODING_DISP64:
    case ZYDIS_OPERAND_ENCODING_DISP16_32_64:
    case ZYDIS_OPERAND_ENCODING_DISP32_32_64:
    case ZYDIS_OPERAND_ENCODING_DISP16_32_32:
        return ZYAN_TRUE;
    case ZYDIS_OPERAND_ENCODING_UIMM8:
    case ZYDIS_OPERAND_ENCODING_UIMM16:
    case ZYDIS_OPERAND_ENCODING_UIMM32:
    case ZYDIS_OPERAND_ENCODING_UIMM64:
    case ZYDIS_OPERAND_ENCODING_UIMM16_32_64:
    case ZYDIS_OPERAND_ENCODING_UIMM32_32_64:
    case ZYDIS_OPERAND_ENCODING_UIMM16_32_32:
    case ZYDIS_OPERAND_ENCODING_IS4:
        return ZYAN_FALSE;
    default:
        ZYAN_UNREACHABLE;
    }
}

/**
 * Calculates effective immediate size.
 *
 * @param   match   A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   imm     Immediate value to encode.
 * @param   def_op  Operand definition for immediate operand.
 *
 * @return  Effective operand size in bits (0 if function failed).
 */

static ZyanU8 ZydisGetEffectiveImmSize(ZydisEncoderInstructionMatch *match, ZyanI64 imm,
    const ZydisOperandDefinition *def_op)
{
    ZyanU8 eisz = 0;
    ZyanU8 min_size = ZydisIsImmSigned((ZydisOperandEncoding)def_op->op.encoding)
        ? ZydisGetSignedImmSize(imm)
        : ZydisGetUnsignedImmSize((ZyanU64)imm);

    switch (def_op->op.encoding)
    {
    case ZYDIS_OPERAND_ENCODING_UIMM8:
    case ZYDIS_OPERAND_ENCODING_SIMM8:
        eisz = 8;
        break;
    case ZYDIS_OPERAND_ENCODING_IS4:
        ZYAN_ASSERT(def_op->element_type == ZYDIS_IELEMENT_TYPE_UINT8);
        eisz = ((ZyanU64)imm <= 15) ? 8 : 0;
        break;
    case ZYDIS_OPERAND_ENCODING_UIMM16:
    case ZYDIS_OPERAND_ENCODING_SIMM16:
        eisz = 16;
        break;
    case ZYDIS_OPERAND_ENCODING_UIMM32:
    case ZYDIS_OPERAND_ENCODING_SIMM32:
        eisz = 32;
        break;
    case ZYDIS_OPERAND_ENCODING_UIMM64:
    case ZYDIS_OPERAND_ENCODING_SIMM64:
        eisz = 64;
        break;
    case ZYDIS_OPERAND_ENCODING_UIMM16_32_64:
    case ZYDIS_OPERAND_ENCODING_SIMM16_32_64:
    {
        static const ZyanU16 simm16_32_64_sizes[3] = { 16, 32, 64 };
        return ZydisGetScaledImmSize(match, simm16_32_64_sizes, min_size);
    }
    case ZYDIS_OPERAND_ENCODING_UIMM32_32_64:
    case ZYDIS_OPERAND_ENCODING_SIMM32_32_64:
    {
        static const ZyanU16 simm32_32_64_sizes[3] = { 32, 32, 64 };
        return ZydisGetScaledImmSize(match, simm32_32_64_sizes, min_size);
    }
    case ZYDIS_OPERAND_ENCODING_UIMM16_32_32:
    case ZYDIS_OPERAND_ENCODING_SIMM16_32_32:
    {
        static const ZyanU16 simm16_32_32_sizes[3] = { 16, 32, 32 };
        return ZydisGetScaledImmSize(match, simm16_32_32_sizes, min_size);
    }
    case ZYDIS_OPERAND_ENCODING_DISP16_32_64:
    {
        ZYAN_ASSERT(match->easz == 0);
        const ZyanU8 addr_size = ZydisGetMaxAddressSize(match->request);
        const ZyanU64 uimm = imm & (~(0xFFFFFFFFFFFFFFFFULL << (addr_size - 1) << 1));
        if (min_size < addr_size && ZydisGetUnsignedImmSize(uimm) > min_size)
        {
            min_size = addr_size;
        }
        if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)
        {
            if (min_size < 32)
            {
                min_size = 32;
            }
            match->easz = eisz = min_size;
        }
        else
        {
            if (min_size < 16)
            {
                min_size = 16;
            }
            if (min_size == 16 || min_size == 32)
            {
                match->easz = eisz = min_size;
            }
        }
        break;
    }
    case ZYDIS_OPERAND_ENCODING_JIMM8:
    case ZYDIS_OPERAND_ENCODING_JIMM16:
    case ZYDIS_OPERAND_ENCODING_JIMM32:
    case ZYDIS_OPERAND_ENCODING_JIMM64:
    {
        ZyanU8 jimm_index = def_op->op.encoding - ZYDIS_OPERAND_ENCODING_JIMM8;
        if ((match->request->branch_width != ZYDIS_BRANCH_WIDTH_NONE) &&
            (match->request->branch_width != (ZydisBranchWidth)(ZYDIS_BRANCH_WIDTH_8 + jimm_index)))
        {
            return 0;
        }
        eisz = 8 << jimm_index;
        break;
    }
    case ZYDIS_OPERAND_ENCODING_JIMM16_32_32:
        switch (match->request->branch_width)
        {
        case ZYDIS_BRANCH_WIDTH_NONE:
        {
            static const ZyanU16 jimm16_32_32_sizes[3] = { 16, 32, 32 };
            return ZydisGetScaledImmSize(match, jimm16_32_32_sizes, min_size);
        }
        case ZYDIS_BRANCH_WIDTH_16:
            eisz = 16;
            break;
        case ZYDIS_BRANCH_WIDTH_32:
            eisz = 32;
            break;
        case ZYDIS_BRANCH_WIDTH_8:
        case ZYDIS_BRANCH_WIDTH_64:
            return 0;
        default:
            ZYAN_UNREACHABLE;
        }
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    return eisz >= min_size ? eisz : 0;
}

/**
 * Checks if register width is compatible with effective operand size.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   reg_width   Register width in bits.
 *
 * @return  True if width is compatible, false otherwise.
 */

static ZyanBool ZydisCheckOsz(ZydisEncoderInstructionMatch *match, ZydisRegisterWidth reg_width)
{
    ZYAN_ASSERT(reg_width <= ZYAN_UINT8_MAX);
    if (match->eosz == 0)
    {
        if (reg_width == 8)
        {
            return ZYAN_FALSE;
        }
        match->eosz = (ZyanU8)reg_width;
        return ZYAN_TRUE;
    }

    return match->eosz == (ZyanU8)reg_width ? ZYAN_TRUE : ZYAN_FALSE;
}

/**
 * Checks if register width is compatible with effective address size.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   reg_width   Register width in bits.
 *
 * @return  True if width is compatible, false otherwise.
 */

static ZyanBool ZydisCheckAsz(ZydisEncoderInstructionMatch *match, ZydisRegisterWidth reg_width)
{
    ZYAN_ASSERT(reg_width <= ZYAN_UINT8_MAX);
    if (match->easz == 0)
    {
        if ((match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) &&
            (reg_width == 16))
        {
            return ZYAN_FALSE;
        }
        match->easz = (ZyanU8)reg_width;
        return ZYAN_TRUE;
    }

    return match->easz == (ZyanU8)reg_width ? ZYAN_TRUE : ZYAN_FALSE;
}

/**
 * Checks if specified register is valid for provided register class, encoding and machine mode.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   reg         `ZydisRegister` value.
 * @param   reg_class   Register class.
 *
 * @return  True if register value is allowed, false otherwise.
 */

static ZyanBool ZydisIsRegisterAllowed(ZydisEncoderInstructionMatch *match, ZydisRegister reg,
    ZydisRegisterClass reg_class)
{
    const ZyanI8 reg_id = ZydisRegisterGetId(reg);
    ZYAN_ASSERT(reg_id >= 0 && reg_id <= 31);
    if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)
    {
        if ((match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_EVEX) &&
            (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_MVEX) &&
            (reg_class != ZYDIS_REGCLASS_GPR8) &&
            (reg_id >= 16))
        {
            return ZYAN_FALSE;
        }
    }
    else
    {
        if (reg_class == ZYDIS_REGCLASS_GPR64)
        {
            return ZYAN_FALSE;
        }
        if (reg_id >= 8)
        {
            return ZYAN_FALSE;
        }
    }

    return ZYAN_TRUE;
}

/**
 * Checks if specified scale value is valid for use with SIB addressing.
 *
 * @param   scale Scale value.
 *
 * @return  True if value is valid, false otherwise.
 */

static ZyanBool ZydisIsScaleValid(ZyanU8 scale)
{
    switch (scale)
    {
    case 0:
    case 1:
    case 2:
    case 4:
    case 8:
        return ZYAN_TRUE;
    default:
        return ZYAN_FALSE;
    }
}

/**
 * Enforces register usage constraints associated with usage of `REX` prefix.
 *
 * @param   match               A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   reg                 `ZydisRegister` value.
 * @param   addressing_mode     True if checked address is used for address calculations. This
 *                              implies more permissive checks.
 *
 * @return  True if register usage is allowed, false otherwise.
 */

static ZyanBool ZydisValidateRexType(ZydisEncoderInstructionMatch *match, ZydisRegister reg,
    ZyanBool addressing_mode)
{
    switch (reg)
    {
    case ZYDIS_REGISTER_AL:
    case ZYDIS_REGISTER_CL:
    case ZYDIS_REGISTER_DL:
    case ZYDIS_REGISTER_BL:
        return ZYAN_TRUE;
    case ZYDIS_REGISTER_AH:
    case ZYDIS_REGISTER_CH:
    case ZYDIS_REGISTER_DH:
    case ZYDIS_REGISTER_BH:
        if (match->rex_type == ZYDIS_REX_TYPE_UNKNOWN)
        {
            match->rex_type = ZYDIS_REX_TYPE_FORBIDDEN;
        }
        else if (match->rex_type == ZYDIS_REX_TYPE_REQUIRED)
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_REGISTER_SPL:
    case ZYDIS_REGISTER_BPL:
    case ZYDIS_REGISTER_SIL:
    case ZYDIS_REGISTER_DIL:
    case ZYDIS_REGISTER_R8B:
    case ZYDIS_REGISTER_R9B:
    case ZYDIS_REGISTER_R10B:
    case ZYDIS_REGISTER_R11B:
    case ZYDIS_REGISTER_R12B:
    case ZYDIS_REGISTER_R13B:
    case ZYDIS_REGISTER_R14B:
    case ZYDIS_REGISTER_R15B:
        if (match->rex_type == ZYDIS_REX_TYPE_UNKNOWN)
        {
            match->rex_type = ZYDIS_REX_TYPE_REQUIRED;
        }
        else if (match->rex_type == ZYDIS_REX_TYPE_FORBIDDEN)
        {
            return ZYAN_FALSE;
        }
        break;
    default:
        if ((ZydisRegisterGetId(reg) > 7) ||
            (!addressing_mode && (ZydisRegisterGetClass(reg) == ZYDIS_REGCLASS_GPR64)))
        {
            if (match->rex_type == ZYDIS_REX_TYPE_UNKNOWN)
            {
                match->rex_type = ZYDIS_REX_TYPE_REQUIRED;
            }
            else if (match->rex_type == ZYDIS_REX_TYPE_FORBIDDEN)
            {
                return ZYAN_FALSE;
            }
        }
        break;
    }

    return ZYAN_TRUE;
}

/**
 * Checks if specified register is valid for use with SIB addressing.
 *
 * @param   match          A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   reg_class      Register class.
 * @param   reg            `ZydisRegister` value.
 *
 * @return  True if register value is allowed, false otherwise.
 */

static ZyanBool ZydisIsValidAddressingClass(ZydisEncoderInstructionMatch *match,
    ZydisRegisterClass reg_class, ZydisRegister reg)
{
    ZyanBool result;
    const ZyanBool is_64 = (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64);
    switch (reg_class)
    {
    case ZYDIS_REGCLASS_INVALID:
        return ZYAN_TRUE;
    case ZYDIS_REGCLASS_GPR16:
        result = !is_64;
        break;
    case ZYDIS_REGCLASS_GPR32:
        result = is_64 || ZydisRegisterGetId(reg) < 8;
        break;
    case ZYDIS_REGCLASS_GPR64:
        result = is_64;
        break;
    default:
        return ZYAN_FALSE;
    }

    return result && ZydisValidateRexType(match, reg, ZYAN_TRUE);
}

/**
 * Helper function that determines correct `ModR/M.RM` value for 16-bit addressing mode.
 *
 * @param   base   `ZydisRegister` used as `SIB.base`.
 * @param   index  `ZydisRegister` used as `SIB.index`.
 *
 * @return  `ModR/M.RM` value (-1 if function failed).
 */

static ZyanI8 ZydisGetRm16(ZydisRegister base, ZydisRegister index)
{
    static const ZydisRegister modrm16_lookup[8][2] =
    {
        { ZYDIS_REGISTER_BX, ZYDIS_REGISTER_SI },
        { ZYDIS_REGISTER_BX, ZYDIS_REGISTER_DI },
        { ZYDIS_REGISTER_BP, ZYDIS_REGISTER_SI },
        { ZYDIS_REGISTER_BP, ZYDIS_REGISTER_DI },
        { ZYDIS_REGISTER_SI, ZYDIS_REGISTER_NONE },
        { ZYDIS_REGISTER_DI, ZYDIS_REGISTER_NONE },
        { ZYDIS_REGISTER_BP, ZYDIS_REGISTER_NONE },
        { ZYDIS_REGISTER_BX, ZYDIS_REGISTER_NONE },
    };
    for (ZyanI8 i = 0; i < (ZyanI8)ZYAN_ARRAY_LENGTH(modrm16_lookup); ++i)
    {
        if ((modrm16_lookup[i][0] == base) &&
            (modrm16_lookup[i][1] == index))
        {
            return i;
        }
    }

    return -1;
}

/**
 * Encodes `MVEX.sss` field for specified broadcast mode.
 *
 * @param   broadcast Broadcast mode.
 *
 * @return  Corresponding `MVEX.sss` value.
 */

static ZyanU8 ZydisEncodeMvexBroadcastMode(ZydisBroadcastMode broadcast)
{
    switch (broadcast)
    {
    case ZYDIS_BROADCAST_MODE_INVALID:
        return 0;
    case ZYDIS_BROADCAST_MODE_1_TO_16:
    case ZYDIS_BROADCAST_MODE_1_TO_8:
        return 1;
    case ZYDIS_BROADCAST_MODE_4_TO_16:
    case ZYDIS_BROADCAST_MODE_4_TO_8:
        return 2;
    default:
        ZYAN_UNREACHABLE;
    }
}

/**
 * Encodes `MVEX.sss` field for specified conversion mode.
 *
 * @param   conversion Conversion mode.
 *
 * @return  Corresponding `MVEX.sss` value.
 */

static ZyanU8 ZydisEncodeMvexConversionMode(ZydisConversionMode conversion)
{
    switch (conversion)
    {
    case ZYDIS_CONVERSION_MODE_INVALID:
        return 0;
    case ZYDIS_CONVERSION_MODE_FLOAT16:
        return 3;
    case ZYDIS_CONVERSION_MODE_UINT8:
        return 4;
    case ZYDIS_CONVERSION_MODE_SINT8:
        return 5;
    case ZYDIS_CONVERSION_MODE_UINT16:
        return 6;
    case ZYDIS_CONVERSION_MODE_SINT16:
        return 7;
    default:
        ZYAN_UNREACHABLE;
    }
}

/**
 * Determines scale factor for compressed 8-bit displacement (`EVEX` instructions only).
 *
 * @param   match   A pointer to `ZydisEncoderInstructionMatch` struct.
 *
 * @return  log2(scale factor)
 */

static ZyanU8 ZydisGetCompDispScaleEvex(const ZydisEncoderInstructionMatch *match)
{
    const ZydisInstructionDefinitionEVEX *evex_def =
        (const ZydisInstructionDefinitionEVEX *)match->base_definition;

    ZYAN_ASSERT(match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX);
    ZYAN_ASSERT(evex_def->tuple_type);
    ZYAN_ASSERT(evex_def->element_size);
    const ZyanU8 vector_length = match->definition->vector_length - ZYDIS_VECTOR_LENGTH_128;
    static const ZyanU8 size_indexes[ZYDIS_IELEMENT_SIZE_MAX_VALUE + 1] =
    {
        0, 0, 0, 1, 2, 4
    };
    ZYAN_ASSERT(evex_def->element_size < ZYAN_ARRAY_LENGTH(size_indexes));
    const ZyanU8 size_index = size_indexes[evex_def->element_size];
    switch (evex_def->tuple_type)
    {
    case ZYDIS_TUPLETYPE_FV:
    {
        static const ZyanU8 scales[2][3][3] =
        {
            /*B0*/ { /*16*/ { 4, 5, 6 }, /*32*/ { 4, 5, 6 }, /*64*/ { 4, 5, 6 } },
            /*B1*/ { /*16*/ { 1, 1, 1 }, /*32*/ { 2, 2, 2 }, /*64*/ { 3, 3, 3 } }
        };
        const ZyanU8 broadcast = match->request->evex.broadcast ? 1 : 0;
        ZYAN_ASSERT(size_index < 3);
        return scales[broadcast][size_index][vector_length];
    }
    case ZYDIS_TUPLETYPE_HV:
    {
        static const ZyanU8 scales[2][2][3] =
        {
            /*B0*/ { /*16*/ {  3, 4, 5 }, /*32*/ {  3, 4, 5 } },
            /*B1*/ { /*16*/ {  1, 1, 1 }, /*32*/ {  2, 2, 2 } }
        };
        const ZyanU8 broadcast = match->request->evex.broadcast ? 1 : 0;
        ZYAN_ASSERT(size_index < 3);
        return scales[broadcast][size_index][vector_length];
    }
    case ZYDIS_TUPLETYPE_FVM:
    {
        static const ZyanU8 scales[3] =
        {
            4, 5, 6
        };
        return scales[vector_length];
    }
    case ZYDIS_TUPLETYPE_GSCAT:
    case ZYDIS_TUPLETYPE_T1S:
    {
        static const ZyanU8 scales[6] =
        {
            /*   */ 0,
            /*  8*/ 0,
            /* 16*/ 1,
            /* 32*/ 2,
            /* 64*/ 3,
            /*128*/ 4
        };
        ZYAN_ASSERT(evex_def->element_size < ZYAN_ARRAY_LENGTH(scales));
        return scales[evex_def->element_size];
    }
    case ZYDIS_TUPLETYPE_T1F:
    {
        static const ZyanU8 scales[3] =
        {
            /* 16*/ 1,
            /* 32*/ 2,
            /* 64*/ 3
        };
        ZYAN_ASSERT(size_index < 3);
        return scales[size_index];
    }
    case ZYDIS_TUPLETYPE_T1_4X:
        return 4;
    case ZYDIS_TUPLETYPE_T2:
        return match->definition->rex_w ? 4 : 3;
    case ZYDIS_TUPLETYPE_T4:
        return match->definition->rex_w ? 5 : 4;
    case ZYDIS_TUPLETYPE_T8:
        return 5;
    case ZYDIS_TUPLETYPE_HVM:
    {
        static const ZyanU8 scales[3] =
        {
            3, 4, 5
        };
        return scales[vector_length];
    }
    case ZYDIS_TUPLETYPE_QVM:
    {
        static const ZyanU8 scales[3] =
        {
            2, 3, 4
        };
        return scales[vector_length];
    }
    case ZYDIS_TUPLETYPE_OVM:
    {
        static const ZyanU8 scales[3] =
        {
            1, 2, 3
        };
        return scales[vector_length];
    }
    case ZYDIS_TUPLETYPE_M128:
        return 4;
    case ZYDIS_TUPLETYPE_DUP:
    {
        static const ZyanU8 scales[3] =
        {
            3, 5, 6
        };
        return scales[vector_length];
    }
    case ZYDIS_TUPLETYPE_QUARTER:
    {
        static const ZyanU8 scales[2][3] =
        {
            /*B0*/ { 2, 3, 4 },
            /*B1*/ { 1, 1, 1 }
        };
        const ZyanU8 broadcast = match->request->evex.broadcast ? 1 : 0;
        return scales[broadcast][vector_length];
    }
    default:
        ZYAN_UNREACHABLE;
    }
}

/**
 * Determines scale factor for compressed 8-bit displacement (`MVEX` instructions only).
 *
 * @param   match   A pointer to `ZydisEncoderInstructionMatch` struct.
 *
 * @return  log2(scale factor)
 */

static ZyanU8 ZydisGetCompDispScaleMvex(const ZydisEncoderInstructionMatch *match)
{
    const ZydisInstructionDefinitionMVEX *mvex_def =
        (const ZydisInstructionDefinitionMVEX *)match->base_definition;

    ZyanU8 index = mvex_def->has_element_granularity;
    ZYAN_ASSERT(!index || !mvex_def->broadcast);
    if (!index && mvex_def->broadcast)
    {
        switch (mvex_def->broadcast)
        {
        case ZYDIS_MVEX_STATIC_BROADCAST_1_TO_8:
        case ZYDIS_MVEX_STATIC_BROADCAST_1_TO_16:
            index = 1;
            break;
        case ZYDIS_MVEX_STATIC_BROADCAST_4_TO_8:
        case ZYDIS_MVEX_STATIC_BROADCAST_4_TO_16:
            index = 2;
            break;
        default:
            ZYAN_UNREACHABLE;
        }
    }

    const ZyanU8 sss = ZydisEncodeMvexBroadcastMode(match->request->mvex.broadcast) |
                       ZydisEncodeMvexConversionMode(match->request->mvex.conversion);
    switch (mvex_def->functionality)
    {
    case ZYDIS_MVEX_FUNC_IGNORED:
    case ZYDIS_MVEX_FUNC_INVALID:
    case ZYDIS_MVEX_FUNC_RC:
    case ZYDIS_MVEX_FUNC_SAE:
    case ZYDIS_MVEX_FUNC_SWIZZLE_32:
    case ZYDIS_MVEX_FUNC_SWIZZLE_64:
        return 0;
    case ZYDIS_MVEX_FUNC_F_32:
    case ZYDIS_MVEX_FUNC_I_32:
    case ZYDIS_MVEX_FUNC_F_64:
    case ZYDIS_MVEX_FUNC_I_64:
        return 6;
    case ZYDIS_MVEX_FUNC_SF_32:
    case ZYDIS_MVEX_FUNC_SF_32_BCST:
    case ZYDIS_MVEX_FUNC_SF_32_BCST_4TO16:
    case ZYDIS_MVEX_FUNC_UF_32:
    {
        static const ZyanU8 lookup[3][8] =
        {
            { 6, 2, 4, 5, 4, 4, 5, 5 },
            { 2, 0, 0, 1, 0, 0, 1, 1 },
            { 4, 0, 0, 3, 2, 2, 3, 3 }
        };
        ZYAN_ASSERT(sss < ZYAN_ARRAY_LENGTH(lookup[index]));
        return lookup[index][sss];
    }
    case ZYDIS_MVEX_FUNC_SI_32:
    case ZYDIS_MVEX_FUNC_UI_32:
    case ZYDIS_MVEX_FUNC_SI_32_BCST:
    case ZYDIS_MVEX_FUNC_SI_32_BCST_4TO16:
    {
        static const ZyanU8 lookup[3][8] =
        {
            { 6, 2, 4, 0, 4, 4, 5, 5 },
            { 2, 0, 0, 0, 0, 0, 1, 1 },
            { 4, 0, 0, 0, 2, 2, 3, 3 }
        };
        ZYAN_ASSERT(sss < ZYAN_ARRAY_LENGTH(lookup[index]));
        return lookup[index][sss];
    }
    case ZYDIS_MVEX_FUNC_SF_64:
    case ZYDIS_MVEX_FUNC_UF_64:
    case ZYDIS_MVEX_FUNC_SI_64:
    case ZYDIS_MVEX_FUNC_UI_64:
    {
        static const ZyanU8 lookup[3][3] =
        {
            { 6, 3, 5 },
            { 3, 0, 0 },
            { 5, 0, 0 }
        };
        ZYAN_ASSERT(sss < ZYAN_ARRAY_LENGTH(lookup[index]));
        return lookup[index][sss];
    }
    case ZYDIS_MVEX_FUNC_DF_32:
    case ZYDIS_MVEX_FUNC_DI_32:
    {
        static const ZyanU8 lookup[2][8] =
        {
            { 6, 0, 0, 5, 4, 4, 5, 5 },
            { 2, 0, 0, 1, 0, 0, 1, 1 }
        };
        ZYAN_ASSERT(index < 2);
        ZYAN_ASSERT(sss < ZYAN_ARRAY_LENGTH(lookup[index]));
        return lookup[index][sss];
    }
    case ZYDIS_MVEX_FUNC_DF_64:
    case ZYDIS_MVEX_FUNC_DI_64:
        ZYAN_ASSERT(index < 2);
        return index == 0 ? 6 : 3;
    default:
        ZYAN_UNREACHABLE;
    }
}

/**
 * Determines scale factor for compressed 8-bit displacement.
 *
 * @param   match A pointer to `ZydisEncoderInstructionMatch` struct.
 *
 * @return  log2(scale factor)
 */

static ZyanU8 ZydisGetCompDispScale(const ZydisEncoderInstructionMatch *match)
{
    switch (match->definition->encoding)
    {
    case ZYDIS_INSTRUCTION_ENCODING_LEGACY:
    case ZYDIS_INSTRUCTION_ENCODING_3DNOW:
    case ZYDIS_INSTRUCTION_ENCODING_XOP:
    case ZYDIS_INSTRUCTION_ENCODING_VEX:
        return 0;
    case ZYDIS_INSTRUCTION_ENCODING_EVEX:
        return ZydisGetCompDispScaleEvex(match);
    case ZYDIS_INSTRUCTION_ENCODING_MVEX:
        return ZydisGetCompDispScaleMvex(match);
    default:
        ZYAN_UNREACHABLE;
    }
}

/**
 * Checks if requested operand matches register operand from instruction definition.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   user_op     Operand definition from `ZydisEncoderRequest` structure.
 * @param   def_op      Decoder's operand definition from current instruction definition.
 *
 * @return  True if operands match, false otherwise.
 */

static ZyanBool ZydisIsRegisterOperandCompatible(ZydisEncoderInstructionMatch *match,
    const ZydisEncoderOperand *user_op, const ZydisOperandDefinition *def_op)
{
    const ZydisRegisterClass reg_class = ZydisRegisterGetClass(user_op->reg.value);
    const ZydisRegisterWidth reg_width = ZydisRegisterClassGetWidth(match->request->machine_mode,
        reg_class);
    if (reg_width == 0)
    {
        return ZYAN_FALSE;
    }

    ZyanBool is4_expected_value = ZYAN_FALSE;
    switch (def_op->type)
    {
    case ZYDIS_SEMANTIC_OPTYPE_IMPLICIT_REG:
        switch (def_op->op.reg.type)
        {
        case ZYDIS_IMPLREG_TYPE_STATIC:
            if (def_op->op.reg.reg.reg != user_op->reg.value)
            {
                return ZYAN_FALSE;
            }
            break;
        case ZYDIS_IMPLREG_TYPE_GPR_OSZ:
            if ((reg_class != ZYDIS_REGCLASS_GPR8) &&
                (reg_class != ZYDIS_REGCLASS_GPR16) &&
                (reg_class != ZYDIS_REGCLASS_GPR32) &&
                (reg_class != ZYDIS_REGCLASS_GPR64))
            {
                return ZYAN_FALSE;
            }
            if (def_op->op.reg.reg.id != ZydisRegisterGetId(user_op->reg.value))
            {
                return ZYAN_FALSE;
            }
            if (!ZydisCheckOsz(match, reg_width))
            {
                return ZYAN_FALSE;
            }
            break;
        case ZYDIS_IMPLREG_TYPE_GPR_ASZ:
            if ((reg_class != ZYDIS_REGCLASS_GPR8) &&
                (reg_class != ZYDIS_REGCLASS_GPR16) &&
                (reg_class != ZYDIS_REGCLASS_GPR32) &&
                (reg_class != ZYDIS_REGCLASS_GPR64))
            {
                return ZYAN_FALSE;
            }
            if (def_op->op.reg.reg.id != ZydisRegisterGetId(user_op->reg.value))
            {
                return ZYAN_FALSE;
            }
            if (!ZydisCheckAsz(match, reg_width))
            {
                return ZYAN_FALSE;
            }
            break;
        default:
            ZYAN_UNREACHABLE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_GPR8:
        if (reg_class != ZYDIS_REGCLASS_GPR8)
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        if (!ZydisValidateRexType(match, user_op->reg.value, ZYAN_FALSE))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_GPR16:
        if (reg_class != ZYDIS_REGCLASS_GPR16)
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_GPR32:
        if (reg_class != ZYDIS_REGCLASS_GPR32)
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_GPR64:
        if (reg_class != ZYDIS_REGCLASS_GPR64)
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_GPR16_32_64:
        if ((reg_class != ZYDIS_REGCLASS_GPR16) &&
            (reg_class != ZYDIS_REGCLASS_GPR32) &&
            (reg_class != ZYDIS_REGCLASS_GPR64))
        {
            return ZYAN_FALSE;
        }
        if (!ZydisCheckOsz(match, reg_width))
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        if (!ZydisValidateRexType(match, user_op->reg.value, ZYAN_FALSE))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_GPR32_32_64:
        if ((reg_class != ZYDIS_REGCLASS_GPR32) &&
            (reg_class != ZYDIS_REGCLASS_GPR64))
        {
            return ZYAN_FALSE;
        }
        if (match->eosz == 0)
        {
            if (reg_class == ZYDIS_REGCLASS_GPR64)
            {
                match->eosz = 64;
            }
            else
            {
                match->eosz64_forbidden = ZYAN_TRUE;
            }
        }
        else if (match->eosz != (ZyanU8)reg_width)
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        if (!ZydisValidateRexType(match, user_op->reg.value, ZYAN_FALSE))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_GPR16_32_32:
        if ((reg_class != ZYDIS_REGCLASS_GPR16) &&
            (reg_class != ZYDIS_REGCLASS_GPR32))
        {
            return ZYAN_FALSE;
        }
        if (!ZydisCheckOsz(match, reg_width))
        {
            if (match->eosz != 64 || reg_class != ZYDIS_REGCLASS_GPR32)
            {
                return ZYAN_FALSE;
            }
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_GPR_ASZ:
        if ((reg_class != ZYDIS_REGCLASS_GPR16) &&
            (reg_class != ZYDIS_REGCLASS_GPR32) &&
            (reg_class != ZYDIS_REGCLASS_GPR64))
        {
            return ZYAN_FALSE;
        }
        if (!ZydisCheckAsz(match, reg_width))
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_FPR:
        if (reg_class != ZYDIS_REGCLASS_X87)
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_MMX:
        if (reg_class != ZYDIS_REGCLASS_MMX)
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_XMM:
        if (reg_class != ZYDIS_REGCLASS_XMM)
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        is4_expected_value = def_op->op.encoding == ZYDIS_OPERAND_ENCODING_IS4;
        break;
    case ZYDIS_SEMANTIC_OPTYPE_YMM:
        if (reg_class != ZYDIS_REGCLASS_YMM)
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        is4_expected_value = def_op->op.encoding == ZYDIS_OPERAND_ENCODING_IS4;
        break;
    case ZYDIS_SEMANTIC_OPTYPE_ZMM:
        if (reg_class != ZYDIS_REGCLASS_ZMM)
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_TMM:
        if (reg_class != ZYDIS_REGCLASS_TMM)
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_BND:
        if (reg_class != ZYDIS_REGCLASS_BOUND)
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_SREG:
        if (reg_class != ZYDIS_REGCLASS_SEGMENT)
        {
            return ZYAN_FALSE;
        }
        if ((def_op->actions & ZYDIS_OPERAND_ACTION_MASK_WRITE) &&
            (user_op->reg.value == ZYDIS_REGISTER_CS))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_CR:
    {
        if (reg_class != ZYDIS_REGCLASS_CONTROL)
        {
            return ZYAN_FALSE;
        }
        static const ZyanU8 cr_lookup[16] =
        {
            1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0
        };
        const ZyanI8 reg_id = ZydisRegisterGetId(user_op->reg.value);
        if ((match->request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64) &&
            (reg_id == 8))
        {
            return ZYAN_FALSE;
        }
        if (!cr_lookup[reg_id])
        {
            return ZYAN_FALSE;
        }
        break;
    }
    case ZYDIS_SEMANTIC_OPTYPE_DR:
        if (reg_class != ZYDIS_REGCLASS_DEBUG)
        {
            return ZYAN_FALSE;
        }
        if (user_op->reg.value >= ZYDIS_REGISTER_DR8)
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_MASK:
        if (reg_class != ZYDIS_REGCLASS_MASK)
        {
            return ZYAN_FALSE;
        }

        // MVEX does not require similar policy check
        if ((match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX) &&
            (def_op->op.encoding == ZYDIS_OPERAND_ENCODING_MASK))
        {
            const ZydisInstructionDefinitionEVEX *evex_def =
                (const ZydisInstructionDefinitionEVEX *)match->base_definition;
            ZYAN_ASSERT((evex_def->mask_policy != ZYDIS_MASK_POLICY_INVALID) &&
                        (evex_def->mask_policy != ZYDIS_MASK_POLICY_FORBIDDEN));
            if ((evex_def->mask_policy == ZYDIS_MASK_POLICY_REQUIRED) &&
                (user_op->reg.value == ZYDIS_REGISTER_K0))
            {
                return ZYAN_FALSE;
            }
            if ((evex_def->mask_policy == ZYDIS_MASK_POLICY_ALLOWED) &&
                (match->request->evex.zeroing_mask) &&
                (user_op->reg.value == ZYDIS_REGISTER_K0))
            {
                return ZYAN_FALSE;
            }
        }
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    if (user_op->reg.is4 != is4_expected_value)
    {
        return ZYAN_FALSE;
    }

    return ZYAN_TRUE;
}

/**
 * Checks if requested operand matches memory operand from instruction definition.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   user_op     Operand definition from `ZydisEncoderRequest` structure.
 * @param   def_op      Decoder's operand definition from current instruction definition.
 *
 * @return  True if operands match, false otherwise.
 */

static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *match,
    const ZydisEncoderOperand *user_op, const ZydisOperandDefinition *def_op)
{
    switch (def_op->type)
    {
    case ZYDIS_SEMANTIC_OPTYPE_MEM:
    case ZYDIS_SEMANTIC_OPTYPE_AGEN:
    case ZYDIS_SEMANTIC_OPTYPE_MIB:
    case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBX:
    case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBY:
    case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBZ:
    {
        if ((def_op->type == ZYDIS_SEMANTIC_OPTYPE_MIB) &&
            (user_op->mem.scale != 0))
        {
            return ZYAN_FALSE;
        }
        const ZyanI64 displacement = user_op->mem.displacement;
        ZyanU8 disp_size = 0;
        if (displacement)
        {
            disp_size = ZydisGetSignedImmSize(displacement);
            if (disp_size > 32)
            {
                return ZYAN_FALSE;
            }

            match->cd8_scale = ZydisGetCompDispScale(match);
            if (match->cd8_scale)
            {
                const ZyanI64 mask = (1 << match->cd8_scale) - 1;
                if (!(displacement & mask))
                {
                    if (ZydisGetSignedImmSize(displacement >> match->cd8_scale) == 8)
                    {
                        disp_size = 8;
                    }
                }
                else if (disp_size == 8)
                {
                    disp_size = 16;
                }
            }
        }

        if (def_op->type != ZYDIS_SEMANTIC_OPTYPE_AGEN)
        {
            if (match->eosz != 0)
            {
                const ZyanU8 eosz_index = match->eosz >> 5;
                if (def_op->size[eosz_index] != user_op->mem.size)
                {
                    return ZYAN_FALSE;
                }
            }
            else if ((match->definition->vector_length != ZYDIS_VECTOR_LENGTH_INVALID) ||
                     (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX))
            {
                ZyanU8 eosz_index = ZydisGetMachineModeWidth(match->request->machine_mode) >> 5;
                if (match->eosz64_forbidden && (eosz_index == 2))
                {
                    eosz_index = 1;
                }
                ZyanU16 allowed_mem_size = def_op->size[eosz_index];
                if ((!allowed_mem_size) &&
                    (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_VEX))
                {
                    ZYAN_ASSERT((match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX) ||
                                (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX));
                    switch (match->definition->vector_length)
                    {
                    case ZYDIS_VECTOR_LENGTH_128:
                        allowed_mem_size = 16;
                        break;
                    case ZYDIS_VECTOR_LENGTH_256:
                        allowed_mem_size = 32;
                        break;
                    case ZYDIS_VECTOR_LENGTH_INVALID:
                        ZYAN_ASSERT(match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX);
                        ZYAN_FALLTHROUGH;
                    case ZYDIS_VECTOR_LENGTH_512:
                        allowed_mem_size = 64;
                        break;
                    default:
                        ZYAN_UNREACHABLE;
                    }
                    if (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX)
                    {
                        const ZydisInstructionDefinitionEVEX *evex_def =
                            (const ZydisInstructionDefinitionEVEX *)match->base_definition;
                        static const ZyanU8 element_sizes[ZYDIS_IELEMENT_SIZE_MAX_VALUE + 1] =
                        {
                              0, 1, 2, 4, 8, 16
                        };
                        ZYAN_ASSERT(evex_def->element_size < ZYAN_ARRAY_LENGTH(element_sizes));
                        const ZyanU8 element_size = element_sizes[evex_def->element_size];
                        if (match->request->evex.broadcast || evex_def->broadcast)
                        {
                            allowed_mem_size = element_size;
                        }
                        else
                        {
                            switch (evex_def->tuple_type)
                            {
                            case ZYDIS_TUPLETYPE_FV:
                                break;
                            case ZYDIS_TUPLETYPE_HV:
                                allowed_mem_size /= 2;
                                break;
                            case ZYDIS_TUPLETYPE_QUARTER:
                                allowed_mem_size /= 4;
                                break;
                            default:
                                ZYAN_UNREACHABLE;
                            }
                        }
                    }
                    else
                    {
                        const ZydisInstructionDefinitionMVEX *mvex_def =
                            (const ZydisInstructionDefinitionMVEX *)match->base_definition;
                        ZyanU16 element_size;
                        switch (match->request->mvex.conversion)
                        {
                        case ZYDIS_CONVERSION_MODE_INVALID:

                            switch (mvex_def->functionality)
                            {
                            case ZYDIS_MVEX_FUNC_SF_32:
                            case ZYDIS_MVEX_FUNC_SF_32_BCST_4TO16:
                            case ZYDIS_MVEX_FUNC_UF_32:
                            case ZYDIS_MVEX_FUNC_DF_32:
                            case ZYDIS_MVEX_FUNC_SI_32:
                            case ZYDIS_MVEX_FUNC_SI_32_BCST_4TO16:
                            case ZYDIS_MVEX_FUNC_UI_32:
                            case ZYDIS_MVEX_FUNC_DI_32:
                                allowed_mem_size = 64;
                                element_size = 4;
                                break;
                            case ZYDIS_MVEX_FUNC_SF_64:
                            case ZYDIS_MVEX_FUNC_UF_64:
                            case ZYDIS_MVEX_FUNC_DF_64:
                            case ZYDIS_MVEX_FUNC_SI_64:
                            case ZYDIS_MVEX_FUNC_UI_64:
                            case ZYDIS_MVEX_FUNC_DI_64:
                                allowed_mem_size = 64;
                                element_size = 8;
                                break;
                            case ZYDIS_MVEX_FUNC_SF_32_BCST:
                            case ZYDIS_MVEX_FUNC_SI_32_BCST:
                                allowed_mem_size = 32;
                                element_size = 4;
                                break;
                            default:
                                ZYAN_UNREACHABLE;
                            }
                            break;
                        case ZYDIS_CONVERSION_MODE_FLOAT16:
                        case ZYDIS_CONVERSION_MODE_SINT16:
                        case ZYDIS_CONVERSION_MODE_UINT16:
                            allowed_mem_size = 32;
                            element_size = 2;
                            break;
                        case ZYDIS_CONVERSION_MODE_SINT8:
                        case ZYDIS_CONVERSION_MODE_UINT8:
                            allowed_mem_size = 16;
                            element_size = 1;
                            break;
                        default:
                            ZYAN_UNREACHABLE;
                        }
                        ZYAN_ASSERT(!mvex_def->broadcast || !match->request->mvex.broadcast);
                        switch (mvex_def->broadcast)
                        {
                        case ZYDIS_MVEX_STATIC_BROADCAST_NONE:
                            break;
                        case ZYDIS_MVEX_STATIC_BROADCAST_1_TO_8:
                        case ZYDIS_MVEX_STATIC_BROADCAST_1_TO_16:
                            allowed_mem_size = element_size;
                            break;
                        case ZYDIS_MVEX_STATIC_BROADCAST_4_TO_8:
                        case ZYDIS_MVEX_STATIC_BROADCAST_4_TO_16:
                            allowed_mem_size = element_size * 4;
                            break;
                        default:
                            ZYAN_UNREACHABLE;
                        }
                        switch (match->request->mvex.broadcast)
                        {
                        case ZYDIS_BROADCAST_MODE_INVALID:
                            break;
                        case ZYDIS_BROADCAST_MODE_1_TO_8:
                        case ZYDIS_BROADCAST_MODE_1_TO_16:
                            allowed_mem_size = element_size;
                            break;
                        case ZYDIS_BROADCAST_MODE_4_TO_8:
                        case ZYDIS_BROADCAST_MODE_4_TO_16:
                            allowed_mem_size = element_size * 4;
                            break;
                        default:
                            ZYAN_UNREACHABLE;
                        }
                    }
                }
                if (user_op->mem.size != allowed_mem_size)
                {
                    return ZYAN_FALSE;
                }
            }
            else if (match->definition->rex_w)
            {
                match->eosz = 64;
            }
            else if (match->definition->vector_length == ZYDIS_VECTOR_LENGTH_INVALID)
            {
                match->eosz = ZydisGetOperandSizeFromElementSize(match, def_op->size,
                    user_op->mem.size, ZYAN_TRUE);
                if (match->eosz == 0)
                {
                    return ZYAN_FALSE;
                }
            }
            else
            {
                ZYAN_UNREACHABLE;
            }
        }
        else
        {
            if (match->easz != 0)
            {
                if (match->easz != user_op->mem.size)
                {
                    return ZYAN_FALSE;
                }
            }
            else
            {
                switch (user_op->mem.size)
                {
                case 2:
                case 4:
                case 8:
                    match->easz = (ZyanU8)user_op->mem.size << 3;
                    break;
                default:
                    return ZYAN_FALSE;
                }
            }
        }

        ZydisRegisterClass vsib_index_class = ZYDIS_REGCLASS_INVALID;
        ZyanBool is_vsib = ZYAN_TRUE;
        switch (def_op->type)
        {
        case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBX:
            vsib_index_class = ZYDIS_REGCLASS_XMM;
            break;
        case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBY:
            vsib_index_class = ZYDIS_REGCLASS_YMM;
            break;
        case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBZ:
            vsib_index_class = ZYDIS_REGCLASS_ZMM;
            break;
        default:
            is_vsib = ZYAN_FALSE;
            break;
        }
        const ZyanBool is_rip_relative = (user_op->mem.base == ZYDIS_REGISTER_RIP) ||
                                         (user_op->mem.base == ZYDIS_REGISTER_EIP);
        if (is_rip_relative)
        {
            const ZyanBool no_rip_rel = ZYDIS_OPDEF_GET_MEM_HIGH_BIT(match->base_definition->op_rm);
            if (no_rip_rel || ((match->definition->modrm & 7) == 4))
            {
                return ZYAN_FALSE;
            }
        }
        const ZydisRegisterClass reg_base_class = ZydisRegisterGetClass(user_op->mem.base);
        if ((reg_base_class == ZYDIS_REGCLASS_INVALID) &&
            (user_op->mem.base != ZYDIS_REGISTER_NONE))
        {
            return ZYAN_FALSE;
        }
        const ZydisRegisterClass reg_index_class = ZydisRegisterGetClass(user_op->mem.index);
        if ((reg_index_class == ZYDIS_REGCLASS_INVALID) &&
            (user_op->mem.index != ZYDIS_REGISTER_NONE))
        {
            return ZYAN_FALSE;
        }
        if (is_vsib)
        {
            const ZyanU8 mode_width = ZydisGetMachineModeWidth(match->request->machine_mode);
            const ZyanI8 reg_index_id = ZydisRegisterGetId(user_op->mem.index);
            if (((match->request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64) ||
                 (reg_base_class != ZYDIS_REGCLASS_GPR64)) &&
                 (reg_base_class != ZYDIS_REGCLASS_GPR32) &&
                 (reg_base_class != ZYDIS_REGCLASS_INVALID))
            {
                return ZYAN_FALSE;
            }
            if ((reg_base_class == ZYDIS_REGCLASS_GPR32) &&
                (mode_width != 64) &&
                (ZydisRegisterGetId(user_op->mem.base) > 7))
            {
                return ZYAN_FALSE;
            }
            ZyanU8 max_reg_id = 7;
            if (mode_width == 64)
            {
                max_reg_id = match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_VEX ?
                    31 : 15;
            }
            if ((reg_index_class != vsib_index_class) ||
                (reg_index_id > max_reg_id))
            {
                return ZYAN_FALSE;
            }
        }
        else
        {
            if (!ZydisIsValidAddressingClass(match, reg_base_class, user_op->mem.base))
            {
                if (!is_rip_relative || match->request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64)
                {
                    return ZYAN_FALSE;
                }
            }
            if (!ZydisIsValidAddressingClass(match, reg_index_class, user_op->mem.index))
            {
                return ZYAN_FALSE;
            }
            if (reg_base_class != ZYDIS_REGCLASS_INVALID &&
                reg_index_class != ZYDIS_REGCLASS_INVALID &&
                reg_base_class != reg_index_class)
            {
                return ZYAN_FALSE;
            }
            if (user_op->mem.index == ZYDIS_REGISTER_ESP ||
                user_op->mem.index == ZYDIS_REGISTER_RSP)
            {
                return ZYAN_FALSE;
            }
        }
        if (reg_index_class != ZYDIS_REGCLASS_INVALID &&
            user_op->mem.scale == 0 &&
            def_op->type != ZYDIS_SEMANTIC_OPTYPE_MIB)
        {
            return ZYAN_FALSE;
        }
        if (reg_index_class == ZYDIS_REGCLASS_INVALID &&
            user_op->mem.scale != 0)
        {
            return ZYAN_FALSE;
        }
        ZyanU8 candidate_easz = 0;
        ZyanBool disp_only = ZYAN_FALSE;
        if (reg_base_class != ZYDIS_REGCLASS_INVALID)
        {
            if (is_rip_relative)
            {
                candidate_easz = user_op->mem.base == ZYDIS_REGISTER_RIP ? 64 : 32;
            }
            else
            {
                candidate_easz = (ZyanU8)ZydisRegisterClassGetWidth(match->request->machine_mode,
                    reg_base_class);
            }
        }
        else if (reg_index_class != ZYDIS_REGCLASS_INVALID)
        {
            if (is_vsib)
            {
                candidate_easz = ZydisGetMachineModeWidth(match->request->machine_mode);
            }
            else
            {
                candidate_easz = (ZyanU8)ZydisRegisterClassGetWidth(match->request->machine_mode,
                    reg_index_class);
            }
        }
        else if (disp_size != 8 || !match->cd8_scale)
        {
            const ZyanU8 addr_size = ZydisGetMaxAddressSize(match->request);
            if (disp_size > addr_size)
            {
                return ZYAN_FALSE;
            }
            ZyanU8 min_disp_size = match->easz ? match->easz : 16;
            if (((min_disp_size == 16) && !(match->definition->address_sizes & ZYDIS_WIDTH_16)) ||
                 (min_disp_size == 64) ||
                 (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64))
            {
                min_disp_size = 32;
            }
            if (disp_size < min_disp_size)
            {
                disp_size = min_disp_size;
            }
            const ZyanI64 disp = user_op->mem.displacement;
            if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)
            {
                candidate_easz = addr_size;
                if (addr_size == 32 && disp >= 0 && match->easz != 32)
                {
                    candidate_easz = 64;
                }
            }
            else
            {
                const ZyanU64 uimm = disp & (~(0xFFFFFFFFFFFFFFFFULL << (addr_size - 1) << 1));
                if (disp_size < addr_size && ZydisGetUnsignedImmSize(uimm) > disp_size)
                {
                    disp_size = addr_size;
                }
                candidate_easz = disp_size;
            }
            disp_only = ZYAN_TRUE;
        }
        if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)
        {
            if (is_rip_relative && reg_index_class != ZYDIS_REGCLASS_INVALID)
            {
                return ZYAN_FALSE;
            }
        }
        else
        {
            if (candidate_easz == 16 && !disp_only)
            {
                if (disp_size > 16)
                {
                    return ZYAN_FALSE;
                }
                const ZyanI8 rm16 = ZydisGetRm16(user_op->mem.base, user_op->mem.index);
                if (rm16 == -1)
                {
                    return ZYAN_FALSE;
                }
                const ZyanU8 allowed_scale = rm16 < 4 ? 1 : 0;
                if (user_op->mem.scale != allowed_scale)
                {
                    return ZYAN_FALSE;
                }
            }
        }
        if (match->easz != 0)
        {
            if (match->easz != candidate_easz)
            {
                return ZYAN_FALSE;
            }
        }
        else
        {
            match->easz = candidate_easz;
        }
        if ((match->base_definition->address_size_map == ZYDIS_ADSIZE_MAP_IGNORED) &&
            (match->easz != ZydisGetMachineModeWidth(match->request->machine_mode)))
        {
            return ZYAN_FALSE;
        }
        match->disp_size = disp_size;
        break;
    }
    case ZYDIS_SEMANTIC_OPTYPE_MOFFS:
    {
        if (user_op->mem.base != ZYDIS_REGISTER_NONE ||
            user_op->mem.index != ZYDIS_REGISTER_NONE ||
            user_op->mem.scale != 0)
        {
            return ZYAN_FALSE;
        }
        const ZyanU8 min_disp_size = ZydisGetSignedImmSize(user_op->mem.displacement);
        if (min_disp_size > ZydisGetMaxAddressSize(match->request))
        {
            return ZYAN_FALSE;
        }
        if (match->eosz != 0)
        {
            const ZyanU8 eosz_index = match->eosz >> 5;
            if (def_op->size[eosz_index] != user_op->mem.size)
            {
                return ZYAN_FALSE;
            }
        }
        else
        {
            match->eosz = ZydisGetOperandSizeFromElementSize(match, def_op->size,
                user_op->mem.size, ZYAN_TRUE);
            if (match->eosz == 0)
            {
                return ZYAN_FALSE;
            }
        }
        match->disp_size = ZydisGetEffectiveImmSize(match, user_op->mem.displacement, def_op);
        if (match->disp_size == 0)
        {
            return ZYAN_FALSE;
        }
        // This is not a standard rejection. It's a special case for `mov` instructions (`moffs`
        // variants only). In 64-bit mode it's possible to get a shorter encoding for addresses
        // that can fit into 32-bit displacements.
        if (match->disp_size == 64 && min_disp_size < match->disp_size)
        {
            return ZYAN_FALSE;
        }
        break;
    }
    default:
        ZYAN_UNREACHABLE;
    }

    return ZYAN_TRUE;
}

/**
 * Checks if requested operand matches pointer operand from instruction definition.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   user_op     Operand definition from `ZydisEncoderRequest` structure.
 *
 * @return  True if operands match, false otherwise.
 */

static ZyanBool ZydisIsPointerOperandCompatible(ZydisEncoderInstructionMatch *match,
    const ZydisEncoderOperand *user_op)
{
    ZYAN_ASSERT(match->eosz == 0);
    ZYAN_ASSERT(match->request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64);
    ZYAN_ASSERT((match->request->branch_type == ZYDIS_BRANCH_TYPE_NONE) ||
                (match->request->branch_type == ZYDIS_BRANCH_TYPE_FAR));
    const ZyanU8 min_disp_size = ZydisGetUnsignedImmSize(user_op->ptr.offset);
    const ZyanU8 desired_disp_size = (match->request->branch_width == ZYDIS_BRANCH_WIDTH_NONE)
        ? ZydisGetMachineModeWidth(match->request->machine_mode)
        : (4 << match->request->branch_width);
    if (min_disp_size > desired_disp_size)
    {
        return ZYAN_FALSE;
    }
    match->eosz = match->disp_size = desired_disp_size;
    match->imm_size = 16;
    return ZYAN_TRUE;
}

/**
 * Checks if requested operand matches immediate operand from instruction definition.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   user_op     Operand definition from `ZydisEncoderRequest` structure.
 * @param   def_op      Decoder's operand definition from current instruction definition.
 *
 * @return  True if operands match, false otherwise.
 */

static ZyanBool ZydisIsImmediateOperandCompabile(ZydisEncoderInstructionMatch *match,
    const ZydisEncoderOperand *user_op, const ZydisOperandDefinition *def_op)
{
    switch (def_op->type)
    {
    case ZYDIS_SEMANTIC_OPTYPE_IMPLICIT_IMM1:
        if (user_op->imm.u != 1)
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_SEMANTIC_OPTYPE_IMM:
    case ZYDIS_SEMANTIC_OPTYPE_REL:
    {
        const ZyanU8 imm_size = ZydisGetEffectiveImmSize(match, user_op->imm.s, def_op);
        if (def_op->op.encoding != ZYDIS_OPERAND_ENCODING_IS4)
        {
            if (imm_size == 0)
            {
                return ZYAN_FALSE;
            }
            if (match->imm_size)
            {
                ZYAN_ASSERT(match->disp_size == 0);
                match->disp_size = match->imm_size;
            }
        }
        else
        {
            ZYAN_ASSERT(match->imm_size == 0);
            if (imm_size != 8)
            {
                return ZYAN_FALSE;
            }
        }
        match->imm_size = imm_size;
        match->has_rel_operand = (def_op->type == ZYDIS_SEMANTIC_OPTYPE_REL);
        break;
    }
    default:
        ZYAN_UNREACHABLE;
    }

    return ZYAN_TRUE;
}

/**
 * Checks if requested boardcast mode is compatible with instruction definition.
 *
 * @param   evex_def       Definition for `EVEX`-encoded instruction.
 * @param   vector_length  Vector length.
 * @param   broadcast      Requested broadcast mode.
 *
 * @return  True if broadcast mode is compatible, false otherwise.
 */

static ZyanBool ZydisIsBroadcastModeCompatible(const ZydisInstructionDefinitionEVEX *evex_def,
    ZydisVectorLength vector_length, ZydisBroadcastMode broadcast)
{
    if (broadcast == ZYDIS_BROADCAST_MODE_INVALID)
    {
        return ZYAN_TRUE;
    }

    ZyanU8 vector_size = 0;
    ZYAN_ASSERT(vector_length != ZYDIS_VECTOR_LENGTH_INVALID);
    switch (vector_length)
    {
    case ZYDIS_VECTOR_LENGTH_128:
        vector_size = 16;
        break;
    case ZYDIS_VECTOR_LENGTH_256:
        vector_size = 32;
        break;
    case ZYDIS_VECTOR_LENGTH_512:
        vector_size = 64;
        break;
    default:
        ZYAN_UNREACHABLE;
    }
    switch (evex_def->tuple_type)
    {
    case ZYDIS_TUPLETYPE_FV:
        break;
    case ZYDIS_TUPLETYPE_HV:
        vector_size /= 2;
        break;
    case ZYDIS_TUPLETYPE_QUARTER:
        vector_size /= 4;
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    ZyanU8 element_size;
    switch (evex_def->element_size)
    {
    case ZYDIS_IELEMENT_SIZE_16:
        element_size = 2;
        break;
    case ZYDIS_IELEMENT_SIZE_32:
        element_size = 4;
        break;
    case ZYDIS_IELEMENT_SIZE_64:
        element_size = 8;
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    ZydisBroadcastMode allowed_mode;
    const ZyanU8 element_count = vector_size / element_size;
    switch (element_count)
    {
    case 2:
        allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_2;
        break;
    case 4:
        allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_4;
        break;
    case 8:
        allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_8;
        break;
    case 16:
        allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_16;
        break;
    case 32:
        allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_32;
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    if (broadcast != allowed_mode)
    {
        return ZYAN_FALSE;
    }

    return ZYAN_TRUE;
}

/**
 * Checks if requested `EVEX`-specific features are compatible with instruction definition.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   request     A pointer to `ZydisEncoderRequest` struct.
 *
 * @return  True if features are compatible, false otherwise.
 */

static ZyanBool ZydisAreEvexFeaturesCompatible(const ZydisEncoderInstructionMatch *match,
    const ZydisEncoderRequest *request)
{
    if (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_EVEX)
    {
        return ZYAN_TRUE;
    }

    const ZydisInstructionDefinitionEVEX *evex_def =
        (const ZydisInstructionDefinitionEVEX *)match->base_definition;
    if ((!evex_def->accepts_zero_mask) &&
        (evex_def->mask_override != ZYDIS_MASK_OVERRIDE_ZEROING) &&
        (request->evex.zeroing_mask))
    {
        return ZYAN_FALSE;
    }

    switch (evex_def->functionality)
    {
    case ZYDIS_EVEX_FUNC_INVALID:
        if ((request->evex.sae) ||
            (request->evex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) ||
            (request->evex.rounding != ZYDIS_ROUNDING_MODE_INVALID))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_EVEX_FUNC_BC:
        if ((request->evex.sae) ||
            (request->evex.rounding != ZYDIS_ROUNDING_MODE_INVALID))
        {
            return ZYAN_FALSE;
        }
        if (!ZydisIsBroadcastModeCompatible(evex_def, match->definition->vector_length,
            request->evex.broadcast))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_EVEX_FUNC_RC:
        if (request->evex.broadcast != ZYDIS_BROADCAST_MODE_INVALID)
        {
            return ZYAN_FALSE;
        }
        if (request->evex.rounding == ZYDIS_ROUNDING_MODE_INVALID)
        {
            if (request->evex.sae)
            {
                return ZYAN_FALSE;
            }
        }
        else
        {
            if (!request->evex.sae)
            {
                return ZYAN_FALSE;
            }
        }
        break;
    case ZYDIS_EVEX_FUNC_SAE:
        if ((request->evex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) ||
            (request->evex.rounding != ZYDIS_ROUNDING_MODE_INVALID))
        {
            return ZYAN_FALSE;
        }
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    return ZYAN_TRUE;
}

/**
 * Checks if requested `MVEX`-specific features are compatible with instruction definition.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   request     A pointer to `ZydisEncoderRequest` struct.
 *
 * @return  True if features are compatible, false otherwise.
 */

static ZyanBool ZydisAreMvexFeaturesCompatible(const ZydisEncoderInstructionMatch *match,
    const ZydisEncoderRequest *request)
{
    if (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_MVEX)
    {
        return ZYAN_TRUE;
    }
    if (((match->definition->modrm >> 6) == 3) &&
        (request->mvex.eviction_hint))
    {
        return ZYAN_FALSE;
    }

    const ZydisInstructionDefinitionMVEX *mvex_def =
        (const ZydisInstructionDefinitionMVEX *)match->base_definition;
    switch (mvex_def->functionality)
    {
    case ZYDIS_MVEX_FUNC_IGNORED:
    case ZYDIS_MVEX_FUNC_INVALID:
    case ZYDIS_MVEX_FUNC_F_32:
    case ZYDIS_MVEX_FUNC_I_32:
    case ZYDIS_MVEX_FUNC_F_64:
    case ZYDIS_MVEX_FUNC_I_64:
    case ZYDIS_MVEX_FUNC_UF_64:
    case ZYDIS_MVEX_FUNC_UI_64:
    case ZYDIS_MVEX_FUNC_DF_64:
    case ZYDIS_MVEX_FUNC_DI_64:
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) ||
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) ||
            (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.sae))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_RC:
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) ||
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.eviction_hint))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_SAE:
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) ||
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) ||
            (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.eviction_hint))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_SWIZZLE_32:
    case ZYDIS_MVEX_FUNC_SWIZZLE_64:
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) ||
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) ||
            (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.sae))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_SF_32:
        if ((request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.sae))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) &&
            (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_1_TO_16) &&
            (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_16))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_FLOAT16) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT8) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT16) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT16))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_SI_32:
        if ((request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.sae))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) &&
            (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_1_TO_16) &&
            (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_16))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT8) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT8) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT16) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT16))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_SF_32_BCST:
    case ZYDIS_MVEX_FUNC_SI_32_BCST:
        if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) ||
            (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.sae))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) &&
            (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_1_TO_16) &&
            (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_16))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_SF_32_BCST_4TO16:
    case ZYDIS_MVEX_FUNC_SI_32_BCST_4TO16:
        if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) ||
            (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.sae))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) &&
            (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_16))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_SF_64:
    case ZYDIS_MVEX_FUNC_SI_64:
        if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) ||
            (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.sae))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) &&
            (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_1_TO_8) &&
            (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_8))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_UF_32:
    case ZYDIS_MVEX_FUNC_DF_32:
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) ||
            (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.sae))
        {
            return ZYAN_FALSE;
        }
        break;
    case ZYDIS_MVEX_FUNC_UI_32:
    case ZYDIS_MVEX_FUNC_DI_32:
        if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) ||
            (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) ||
            (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) ||
            (request->mvex.sae))
        {
            return ZYAN_FALSE;
        }
        if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT8) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT8) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT16) &&
            (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT16))
        {
            return ZYAN_FALSE;
        }
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    return ZYAN_TRUE;
}

/**
 * Checks if operands specified in encoder request satisfy additional constraints mandated by
 * matched instruction definition.
 *
 * @param   match   A pointer to `ZydisEncoderInstructionMatch` struct.
 *
 * @return  True if operands passed the checks, false otherwise.
 */

static ZyanBool ZydisCheckConstraints(const ZydisEncoderInstructionMatch *match)
{
    const ZydisEncoderOperand *operands = match->request->operands;
    ZyanBool is_gather = ZYAN_FALSE;
    switch (match->definition->encoding)
    {
    case ZYDIS_INSTRUCTION_ENCODING_VEX:
    {
        const ZydisInstructionDefinitionVEX *vex_def =
            (const ZydisInstructionDefinitionVEX *)match->base_definition;
        if (vex_def->is_gather)
        {
            ZYAN_ASSERT(match->request->operand_count == 3);
            ZYAN_ASSERT(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER);
            ZYAN_ASSERT(operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY);
            ZYAN_ASSERT(operands[2].type == ZYDIS_OPERAND_TYPE_REGISTER);
            const ZyanI8 dest = ZydisRegisterGetId(operands[0].reg.value);
            const ZyanI8 index = ZydisRegisterGetId(operands[1].mem.index);
            const ZyanI8 mask = ZydisRegisterGetId(operands[2].reg.value);
            // If any pair of the index, mask, or destination registers are the same, the
            // instruction results a UD fault.
            if ((dest == index) || (dest == mask) || (index == mask))
            {
                return ZYAN_FALSE;
            }
        }

        if (vex_def->no_source_source_match)
        {
            ZYAN_ASSERT(match->request->operand_count == 3);
            ZYAN_ASSERT(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER);
            ZYAN_ASSERT(operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER);
            ZYAN_ASSERT(operands[2].type == ZYDIS_OPERAND_TYPE_REGISTER);
            const ZydisRegister dest = operands[0].reg.value;
            const ZydisRegister source1 = operands[1].reg.value;
            const ZydisRegister source2 = operands[2].reg.value;
            // AMX-E4: #UD if srcdest == src1 OR src1 == src2 OR srcdest == src2.
            if ((dest == source1) || (source1 == source2) || (dest == source2))
            {
                return ZYAN_FALSE;
            }
        }

        return ZYAN_TRUE;
    }
    case ZYDIS_INSTRUCTION_ENCODING_EVEX:
    {
        const ZydisInstructionDefinitionEVEX *evex_def =
            (const ZydisInstructionDefinitionEVEX *)match->base_definition;
        is_gather = evex_def->is_gather;
        if (evex_def->no_source_dest_match)
        {
            ZYAN_ASSERT(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER);
            ZYAN_ASSERT(operands[2].type == ZYDIS_OPERAND_TYPE_REGISTER);
            ZYAN_ASSERT((operands[3].type == ZYDIS_OPERAND_TYPE_REGISTER) ||
                        (operands[3].type == ZYDIS_OPERAND_TYPE_MEMORY));
            const ZydisRegister dest = operands[0].reg.value;
            const ZydisRegister source1 = operands[2].reg.value;
            const ZydisRegister source2 = (operands[3].type == ZYDIS_OPERAND_TYPE_REGISTER)
                ? operands[3].reg.value
                : ZYDIS_REGISTER_NONE;

            if ((dest == source1) || (dest == source2))
            {
                return ZYAN_FALSE;
            }
        }
        break;
    }
    case ZYDIS_INSTRUCTION_ENCODING_MVEX:
    {
        const ZydisInstructionDefinitionMVEX *mvex_def =
            (const ZydisInstructionDefinitionMVEX *)match->base_definition;
        is_gather = mvex_def->is_gather;
        break;
    }
    default:
        return ZYAN_TRUE;
    }

    if ((is_gather) && (operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER))
    {
        ZYAN_ASSERT(match->request->operand_count == 3);
        ZYAN_ASSERT(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER);
        ZYAN_ASSERT(operands[2].type == ZYDIS_OPERAND_TYPE_MEMORY);
        const ZyanI8 dest = ZydisRegisterGetId(operands[0].reg.value);
        const ZyanI8 index = ZydisRegisterGetId(operands[2].mem.index);
        // EVEX: The instruction will #UD fault if the destination vector zmm1 is the same as
        // index vector VINDEX.
        // MVEX: The KNC GATHER instructions forbid using the same vector register for destination
        // and for the index. (https://github.com/intelxed/xed/issues/281#issuecomment-970074554)
        if (dest == index)
        {
            return ZYAN_FALSE;
        }
    }

    return ZYAN_TRUE;
}

/**
 * Checks if operands and encoding-specific features from `ZydisEncoderRequest` match
 * encoder's instruction definition.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   request     A pointer to `ZydisEncoderRequest` struct.
 *
 * @return  True if definition is compatible, false otherwise.
 */

static ZyanBool ZydisIsDefinitionCompatible(ZydisEncoderInstructionMatch *match,
    const ZydisEncoderRequest *request)
{
    ZYAN_ASSERT(request->operand_count == match->base_definition->operand_count_visible);
    match->operands = ZydisGetOperandDefinitions(match->base_definition);

    if (!ZydisAreEvexFeaturesCompatible(match, request))
    {
        return ZYAN_FALSE;
    }
    if (!ZydisAreMvexFeaturesCompatible(match, request))
    {
        return ZYAN_FALSE;
    }

    for (ZyanU8 i = 0; i < request->operand_count; ++i)
    {
        const ZydisEncoderOperand *user_op = &request->operands[i];
        const ZydisOperandDefinition *def_op = &match->operands[i];
        ZYAN_ASSERT(def_op->visibility != ZYDIS_OPERAND_VISIBILITY_HIDDEN);
        ZyanBool is_compatible = ZYAN_FALSE;
        switch (user_op->type)
        {
        case ZYDIS_OPERAND_TYPE_REGISTER:
            is_compatible = ZydisIsRegisterOperandCompatible(match, user_op, def_op);
            break;
        case ZYDIS_OPERAND_TYPE_MEMORY:
            is_compatible = ZydisIsMemoryOperandCompatible(match, user_op, def_op);
            break;
        case ZYDIS_OPERAND_TYPE_POINTER:
            is_compatible = ZydisIsPointerOperandCompatible(match, user_op);
            break;
        case ZYDIS_OPERAND_TYPE_IMMEDIATE:
            is_compatible = ZydisIsImmediateOperandCompabile(match, user_op, def_op);
            break;
        default:
            ZYAN_UNREACHABLE;
        }

        if (!is_compatible)
        {
            return ZYAN_FALSE;
        }
    }

    ZyanU8 eosz = 0;
    if (match->base_definition->branch_type != ZYDIS_BRANCH_TYPE_NONE)
    {
        switch (request->branch_width)
        {
        case ZYDIS_BRANCH_WIDTH_NONE:
            break;
        case ZYDIS_BRANCH_WIDTH_8:
            if ((!match->has_rel_operand) ||
                (match->base_definition->branch_type != ZYDIS_BRANCH_TYPE_SHORT))
            {
                return ZYAN_FALSE;
            }
            break;
        case ZYDIS_BRANCH_WIDTH_16:
            eosz = 16;
            break;
        case ZYDIS_BRANCH_WIDTH_32:
            eosz = ((match->has_rel_operand) &&
                    (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) &&
                    (match->base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_FORCE64))
                ? 64
                : 32;
            break;
        case ZYDIS_BRANCH_WIDTH_64:
            if (match->has_rel_operand)
            {
                return ZYAN_FALSE;
            }
            eosz = 64;
            break;
        default:
            ZYAN_UNREACHABLE;
        }
    }
    if (eosz)
    {
        if (match->eosz != 0)
        {
            if (match->eosz != eosz)
            {
                return ZYAN_FALSE;
            }
        }
        else
        {
            match->eosz = eosz;
        }
    }

    if (!ZydisCheckConstraints(match))
    {
        return ZYAN_FALSE;
    }

    return ZYAN_TRUE;
}

/**
 * Checks if requested set of prefixes is compatible with instruction definition.
 *
 * @param   match A pointer to `ZydisEncoderInstructionMatch` struct.
 *
 * @return  A zyan status code.
 */

static ZyanBool ZydisArePrefixesCompatible(const ZydisEncoderInstructionMatch *match)
{
    // Early-exit optimization for when no prefixes are requested at all.
    if (!(match->attributes & ZYDIS_ENCODABLE_PREFIXES))
    {
        return ZYAN_TRUE;
    }

    if ((!match->base_definition->accepts_segment) &&
        (match->attributes & ZYDIS_ATTRIB_HAS_SEGMENT))
    {
        return ZYAN_FALSE;
    }
    if (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_LEGACY)
    {
        return !(match->attributes & ZYDIS_ENCODABLE_PREFIXES_NO_SEGMENTS);
    }

    const ZydisInstructionDefinitionLEGACY *legacy_def =
        (const ZydisInstructionDefinitionLEGACY *)match->base_definition;
    if ((!legacy_def->accepts_LOCK) &&
        (match->attributes & ZYDIS_ATTRIB_HAS_LOCK))
    {
        return ZYAN_FALSE;
    }
    if ((!legacy_def->accepts_REP) &&
        (match->attributes & ZYDIS_ATTRIB_HAS_REP))
    {
        return ZYAN_FALSE;
    }
    if ((!legacy_def->accepts_REPEREPZ) &&
        (match->attributes & ZYDIS_ATTRIB_HAS_REPE))
    {
        return ZYAN_FALSE;
    }
    if ((!legacy_def->accepts_REPNEREPNZ) &&
        (match->attributes & ZYDIS_ATTRIB_HAS_REPNE))
    {
        return ZYAN_FALSE;
    }
    if ((!legacy_def->accepts_BOUND) &&
        (match->attributes & ZYDIS_ATTRIB_HAS_BND))
    {
        return ZYAN_FALSE;
    }
    if ((!legacy_def->accepts_XACQUIRE) &&
        (match->attributes & ZYDIS_ATTRIB_HAS_XACQUIRE))
    {
        return ZYAN_FALSE;
    }
    if ((!legacy_def->accepts_XRELEASE) &&
        (match->attributes & ZYDIS_ATTRIB_HAS_XRELEASE))
    {
        return ZYAN_FALSE;
    }
    if ((!legacy_def->accepts_branch_hints) &&
        (match->attributes & (ZYDIS_ATTRIB_HAS_BRANCH_NOT_TAKEN |
                              ZYDIS_ATTRIB_HAS_BRANCH_TAKEN)))
    {
        return ZYAN_FALSE;
    }
    if ((!legacy_def->accepts_NOTRACK) &&
        (match->attributes & ZYDIS_ATTRIB_HAS_NOTRACK))
    {
        return ZYAN_FALSE;
    }
    if ((!legacy_def->accepts_hle_without_lock) &&
        (match->attributes & (ZYDIS_ATTRIB_HAS_XACQUIRE |
                              ZYDIS_ATTRIB_HAS_XRELEASE)) &&
        !(match->attributes & ZYDIS_ATTRIB_HAS_LOCK))
    {
        return ZYAN_FALSE;
    }

    return ZYAN_TRUE;
}

/**
 * Returns operand mask containing information about operand count and types in a compressed form.
 *
 * @param   request     A pointer to `ZydisEncoderRequest` struct.
 *
 * @return  Operand mask.
 */

static ZyanU16 ZydisGetOperandMask(const ZydisEncoderRequest *request)
{
    ZyanU16 operand_mask = request->operand_count;
    ZyanU8 bit_offset = ZYAN_BITS_TO_REPRESENT(ZYDIS_ENCODER_MAX_OPERANDS);
    for (ZyanU8 i = 0; i < request->operand_count; ++i)
    {
        operand_mask |= (request->operands[i].type - ZYDIS_OPERAND_TYPE_REGISTER) << bit_offset;
        bit_offset += ZYAN_BITS_TO_REPRESENT(
            ZYDIS_OPERAND_TYPE_MAX_VALUE - ZYDIS_OPERAND_TYPE_REGISTER);
    }

    return operand_mask;
}

/**
 * Handles optimization opportunities indicated by `swappable` field in instruction definition
 * structure. See `ZydisEncodableInstruction` for more information.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 *
 * @return  True if definition has been swapped, false otherwise.
 */

static ZyanBool ZydisHandleSwappableDefinition(ZydisEncoderInstructionMatch *match)
{
    if (!match->definition->swappable)
    {
        return ZYAN_FALSE;
    }

    // Special case for ISA-wide unique conflict between two `mov` variants
    // mov gpr16_32_64(encoding=opcode), imm(encoding=simm16_32_64,scale_factor=osz)
    // mov gpr16_32_64(encoding=modrm_rm), imm(encoding=simm16_32_32,scale_factor=osz)
    if (match->request->mnemonic == ZYDIS_MNEMONIC_MOV)
    {
        const ZyanU8 imm_size = ZydisGetSignedImmSize(match->request->operands[1].imm.s);
        if ((match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) &&
            (match->eosz == 64) &&
            (imm_size < 64))
        {
            return ZYAN_TRUE;
        }
    }

    ZYAN_ASSERT((match->request->operand_count == 2) || (match->request->operand_count == 3));
    const ZyanU8 src_index = (match->request->operand_count == 3) ? 2 : 1;
    const ZyanI8 dest_id = ZydisRegisterGetId(match->request->operands[0].reg.value);
    const ZyanI8 src_id = ZydisRegisterGetId(match->request->operands[src_index].reg.value);
    if ((dest_id <= 7) && (src_id > 7))
    {
        ++match->definition;
        ZydisGetInstructionDefinition(match->definition->encoding,
            match->definition->instruction_reference, &match->base_definition);
        match->operands = ZydisGetOperandDefinitions(match->base_definition);
        return ZYAN_TRUE;
    }

    return ZYAN_FALSE;
}

/**
 * This function attempts to find a matching instruction definition for provided encoder request.
 *
 * @param   request     A pointer to `ZydisEncoderRequest` struct.
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisFindMatchingDefinition(const ZydisEncoderRequest *request,
    ZydisEncoderInstructionMatch *match)
{
    ZYAN_MEMSET(match, 0, sizeof(ZydisEncoderInstructionMatch));
    match->request = request;
    match->attributes = request->prefixes;

    const ZydisEncodableInstruction *definition = ZYAN_NULL;
    const ZyanU8 definition_count = ZydisGetEncodableInstructions(request->mnemonic, &definition);
    ZYAN_ASSERT(definition && definition_count);
    const ZydisWidthFlag mode_width = ZydisGetMachineModeWidth(request->machine_mode) >> 4;
    const ZyanBool is_compat =
        (request->machine_mode == ZYDIS_MACHINE_MODE_LONG_COMPAT_16) ||
        (request->machine_mode == ZYDIS_MACHINE_MODE_LONG_COMPAT_32);
    const ZyanU8 default_asz = ZydisGetAszFromHint(request->address_size_hint);
    const ZyanU8 default_osz = ZydisGetOszFromHint(request->operand_size_hint);
    const ZyanU16 operand_mask = ZydisGetOperandMask(request);

    for (ZyanU8 i = 0; i < definition_count; ++i, ++definition)
    {
        if (definition->operand_mask != operand_mask)
        {
            continue;
        }
        const ZydisInstructionDefinition *base_definition = ZYAN_NULL;
        ZydisGetInstructionDefinition(definition->encoding, definition->instruction_reference,
            &base_definition);
        if (!(definition->modes & mode_width))
        {
            continue;
        }
        if ((request->allowed_encodings != ZYDIS_ENCODABLE_ENCODING_DEFAULT) &&
            !(ZydisGetEncodableEncoding(definition->encoding) & request->allowed_encodings))
        {
            continue;
        }
        if (request->machine_mode == ZYDIS_MACHINE_MODE_REAL_16)
        {
            if (base_definition->requires_protected_mode)
            {
                continue;
            }
            switch (definition->encoding)
            {
            case ZYDIS_INSTRUCTION_ENCODING_XOP:
            case ZYDIS_INSTRUCTION_ENCODING_VEX:
            case ZYDIS_INSTRUCTION_ENCODING_EVEX:
            case ZYDIS_INSTRUCTION_ENCODING_MVEX:
                continue;
            default:
                break;
            }
        }
        else if ((request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64) &&
                 (definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX))
        {
            continue;
        }
        if (is_compat && base_definition->no_compat_mode)
        {
            continue;
        }
        if ((request->branch_type != ZYDIS_BRANCH_TYPE_NONE) &&
            (request->branch_type != base_definition->branch_type))
        {
            continue;
        }
        if ((base_definition->branch_type == ZYDIS_BRANCH_TYPE_NONE) &&
            (request->branch_width != ZYDIS_BRANCH_WIDTH_NONE))
        {
            continue;
        }

        match->definition = definition;
        match->base_definition = base_definition;
        match->operands = ZYAN_NULL;
        match->easz = definition->accepts_hint == ZYDIS_SIZE_HINT_ASZ ? default_asz : 0;
        match->eosz = definition->accepts_hint == ZYDIS_SIZE_HINT_OSZ ? default_osz : 0;
        match->disp_size = match->imm_size = match->cd8_scale = 0;
        match->rex_type = ZYDIS_REX_TYPE_UNKNOWN;
        match->eosz64_forbidden = ZYAN_FALSE;
        match->has_rel_operand = ZYAN_FALSE;
        if ((base_definition->operand_size_map != ZYDIS_OPSIZE_MAP_BYTEOP) &&
            (match->eosz == 8))
        {
            continue;
        }
        if (!ZydisArePrefixesCompatible(match))
        {
            continue;
        }
        if (!ZydisIsDefinitionCompatible(match, request))
        {
            continue;
        }
        if (ZydisHandleSwappableDefinition(match))
        {
            if (definition == match->definition)
            {
                continue;
            }
            ++i;
            definition = match->definition;
            base_definition = match->base_definition;
        }

        if (match->easz == 0)
        {
            if (definition->address_sizes & mode_width)
            {
                match->easz = (ZyanU8)(mode_width << 4);
            }
            else if (mode_width == ZYDIS_WIDTH_16)
            {
                match->easz = 32;
            }
            else if (mode_width == ZYDIS_WIDTH_32)
            {
                match->easz = 16;
            }
            else
            {
                match->easz = 32;
            }
            ZYAN_ASSERT(definition->address_sizes & (match->easz >> 4));
        }
        else if (!(definition->address_sizes & (match->easz >> 4)))
        {
            continue;
        }

        if (mode_width == ZYDIS_WIDTH_64)
        {
            if (base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_DEFAULT64)
            {
                if (match->eosz == 0)
                {
                    ZYAN_ASSERT(definition->operand_sizes & (ZYDIS_WIDTH_16 | ZYDIS_WIDTH_64));
                    if (definition->operand_sizes & ZYDIS_WIDTH_64)
                    {
                        match->eosz = 64;
                    }
                    else
                    {
                        match->eosz = 16;
                    }
                }
                else if (match->eosz == 32)
                {
                    continue;
                }
            }
            else if (base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_FORCE64)
            {
                if (match->eosz == 0)
                {
                    match->eosz = 64;
                }
                else if (match->eosz != 64)
                {
                    continue;
                }
            }
        }
        if (match->eosz == 0)
        {
            const ZydisWidthFlag default_width = (mode_width == ZYDIS_WIDTH_64)
                ? ZYDIS_WIDTH_32
                : mode_width;
            if (definition->operand_sizes & default_width)
            {
                match->eosz = (ZyanU8)(default_width << 4);
            }
            else if (definition->operand_sizes & ZYDIS_WIDTH_16)
            {
                match->eosz = 16;
            }
            else if (definition->operand_sizes & ZYDIS_WIDTH_32)
            {
                match->eosz = 32;
            }
            else
            {
                match->eosz = 64;
            }
        }
        else if (match->eosz64_forbidden && match->eosz == 64)
        {
            continue;
        }
        else if (!(definition->operand_sizes & (match->eosz >> 4)))
        {
            continue;
        }

        return ZYAN_STATUS_SUCCESS;
    }

    return ZYDIS_STATUS_IMPOSSIBLE_INSTRUCTION;
}

/**
 * Emits unsigned integer value.
 *
 * @param   data    Value to emit.
 * @param   size    Value size in bytes.
 * @param   buffer  A pointer to `ZydisEncoderBuffer` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEmitUInt(ZyanU64 data, ZyanU8 size, ZydisEncoderBuffer *buffer)
{
    ZYAN_ASSERT(size == 1 || size == 2 || size == 4 || size == 8);

    const ZyanUSize new_offset = buffer->offset + size;
    if (new_offset > buffer->size)
    {
        return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
    }

    // TODO: fix for big-endian systems
    // The size variable is not passed on purpose to allow the compiler
    // to generate better code with a known size at compile time.
    if (size == 1)
    {
        ZYAN_MEMCPY(buffer->buffer + buffer->offset, &data, 1);
    }
    else if (size == 2)
    {
        ZYAN_MEMCPY(buffer->buffer + buffer->offset, &data, 2);
    }
    else if (size == 4)
    {
        ZYAN_MEMCPY(buffer->buffer + buffer->offset, &data, 4);
    }
    else if (size == 8)
    {
        ZYAN_MEMCPY(buffer->buffer + buffer->offset, &data, 8);
    }
    else
    {
        ZYAN_UNREACHABLE;
    }

    buffer->offset = new_offset;
    return ZYAN_STATUS_SUCCESS;
}

/**
 * Emits a single byte.
 *
 * @param   byte    Value to emit.
 * @param   buffer  A pointer to `ZydisEncoderBuffer` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEmitByte(ZyanU8 byte, ZydisEncoderBuffer *buffer)
{
    return ZydisEmitUInt(byte, 1, buffer);
}

/**
 * Emits legact prefixes.
 *
 * @param   instruction     A pointer to `ZydisEncoderInstruction` struct.
 * @param   buffer          A pointer to `ZydisEncoderBuffer` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEmitLegacyPrefixes(const ZydisEncoderInstruction *instruction,
    ZydisEncoderBuffer *buffer)
{
    ZyanBool compressed_prefixes = ZYAN_FALSE;
    switch (instruction->encoding)
    {
    case ZYDIS_INSTRUCTION_ENCODING_XOP:
    case ZYDIS_INSTRUCTION_ENCODING_VEX:
    case ZYDIS_INSTRUCTION_ENCODING_EVEX:
    case ZYDIS_INSTRUCTION_ENCODING_MVEX:
        compressed_prefixes = ZYAN_TRUE;
        break;
    default:
        break;
    }

    // Group 1
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_LOCK)
    {
        ZYAN_CHECK(ZydisEmitByte(0xF0, buffer));
    }
    if (!compressed_prefixes)
    {
        if (instruction->attributes & (ZYDIS_ATTRIB_HAS_REPNE |
                                       ZYDIS_ATTRIB_HAS_BND |
                                       ZYDIS_ATTRIB_HAS_XACQUIRE))
        {
            ZYAN_CHECK(ZydisEmitByte(0xF2, buffer));
        }
        if (instruction->attributes & (ZYDIS_ATTRIB_HAS_REP |
                                       ZYDIS_ATTRIB_HAS_REPE |
                                       ZYDIS_ATTRIB_HAS_XRELEASE))
        {
            ZYAN_CHECK(ZydisEmitByte(0xF3, buffer));
        }
    }

    // Group 2
    if (instruction->attributes & (ZYDIS_ATTRIB_HAS_SEGMENT_CS |
                                   ZYDIS_ATTRIB_HAS_BRANCH_NOT_TAKEN))
    {
        ZYAN_CHECK(ZydisEmitByte(0x2E, buffer));
    }
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_SEGMENT_SS)
    {
        ZYAN_CHECK(ZydisEmitByte(0x36, buffer));
    }
    if (instruction->attributes & (ZYDIS_ATTRIB_HAS_SEGMENT_DS |
                                   ZYDIS_ATTRIB_HAS_BRANCH_TAKEN))
    {
        ZYAN_CHECK(ZydisEmitByte(0x3E, buffer));
    }
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_SEGMENT_ES)
    {
        ZYAN_CHECK(ZydisEmitByte(0x26, buffer));
    }
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_SEGMENT_FS)
    {
        ZYAN_CHECK(ZydisEmitByte(0x64, buffer));
    }
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_SEGMENT_GS)
    {
        ZYAN_CHECK(ZydisEmitByte(0x65, buffer));
    }
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_NOTRACK)
    {
        ZYAN_CHECK(ZydisEmitByte(0x3E, buffer));
    }

    // Group 3
    if (!compressed_prefixes)
    {
        if (instruction->attributes & ZYDIS_ATTRIB_HAS_OPERANDSIZE)
        {
            ZYAN_CHECK(ZydisEmitByte(0x66, buffer));
        }
    }

    // Group 4
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_ADDRESSSIZE)
    {
        ZYAN_CHECK(ZydisEmitByte(0x67, buffer));
    }

    return ZYAN_STATUS_SUCCESS;
}

/**
 * Encodes low nibble of `REX` prefix.
 *
 * @param   instruction     A pointer to `ZydisEncoderInstruction` struct.
 * @param   high_r          A pointer to `ZyanBool` variable that will be set to true when the
 *                          highest `ModR/M.reg` bit cannot be encoded using `REX` prefix.
 *
 * @return  A zyan status code.
 */

static ZyanU8 ZydisEncodeRexLowNibble(const ZydisEncoderInstruction *instruction, ZyanBool *high_r)
{
    if (high_r)
    {
        *high_r = ZYAN_FALSE;
    }

    ZyanU8 rex = 0;
    if ((instruction->attributes & ZYDIS_ATTRIB_HAS_MODRM) &&
        (instruction->attributes & ZYDIS_ATTRIB_HAS_SIB))
    {
        if (instruction->base & 0x08)
        {
            rex |= 1;
        }
        if (instruction->index & 0x08)
        {
            rex |= 2;
        }
        if (instruction->reg & 0x08)
        {
            rex |= 4;
        }
        if (high_r && (instruction->reg & 0x10))
        {
            *high_r = ZYAN_TRUE;
        }
    }
    else if (instruction->attributes & ZYDIS_ATTRIB_HAS_MODRM)
    {
        if (instruction->rm & 0x08)
        {
            rex |= 1;
        }
        if (instruction->rm & 0x10)
        {
            rex |= 2;
        }
        if (instruction->reg & 0x08)
        {
            rex |= 4;
        }
        if (high_r && (instruction->reg & 0x10))
        {
            *high_r = ZYAN_TRUE;
        }
    }
    else
    {
        if (instruction->rm & 0x08)
        {
            rex |= 1;
        }
    }

    if (instruction->rex_w)
    {
        rex |= 8;
    }

    return rex;
}

/**
 * Emits `REX` prefix.
 *
 * @param   instruction     A pointer to `ZydisEncoderInstruction` struct.
 * @param   buffer          A pointer to `ZydisEncoderBuffer` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEmitRex(const ZydisEncoderInstruction *instruction,
    ZydisEncoderBuffer *buffer)
{
    const ZyanU8 rex = ZydisEncodeRexLowNibble(instruction, ZYAN_NULL);
    if (rex || (instruction->attributes & ZYDIS_ATTRIB_HAS_REX))
    {
        ZYAN_CHECK(ZydisEmitByte(0x40 | rex, buffer));
    }

    return ZYAN_STATUS_SUCCESS;
}

/**
 * Encodes common parts of `VEX` prefix.
 *
 * @param   instruction     A pointer to `ZydisEncoderInstruction` struct.
 * @param   mmmmm           A pointer to `ZyanU8` variable that will receive `VEX.mmmmm`
 * @param   pp              A pointer to `ZyanU8` variable that will receive `VEX.pp`
 * @param   vvvv            A pointer to `ZyanU8` variable that will receive `VEX.vvvv`
 * @param   rex             A pointer to `ZyanU8` variable that will receive 'REX`
 * @param   high_r          A pointer to `ZyanBool` variable that will be set to true when the
 *                          highest `ModR/M.reg` bit cannot be encoded using `REX` prefix.
 */

static void ZydisEncodeVexCommons(ZydisEncoderInstruction *instruction, ZyanU8 *mmmmm, ZyanU8 *pp,
    ZyanU8 *vvvv, ZyanU8 *rex, ZyanBool *high_r)
{
    switch (instruction->opcode_map)
    {
    case ZYDIS_OPCODE_MAP_DEFAULT:
    case ZYDIS_OPCODE_MAP_0F:
    case ZYDIS_OPCODE_MAP_0F38:
    case ZYDIS_OPCODE_MAP_0F3A:
    case ZYDIS_OPCODE_MAP_MAP5:
    case ZYDIS_OPCODE_MAP_MAP6:
        *mmmmm = (ZyanU8)instruction->opcode_map;
        break;
    case ZYDIS_OPCODE_MAP_XOP8:
    case ZYDIS_OPCODE_MAP_XOP9:
    case ZYDIS_OPCODE_MAP_XOPA:
        *mmmmm = 8 + ((ZyanU8)instruction->opcode_map - ZYDIS_OPCODE_MAP_XOP8);
        break;
    default:
        ZYAN_UNREACHABLE;
    }
    instruction->opcode_map = ZYDIS_OPCODE_MAP_DEFAULT;

    *pp = 0;
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_OPERANDSIZE)
    {
        *pp = 1;
    }
    else if (instruction->attributes & ZYDIS_ATTRIB_HAS_REP)
    {
        *pp = 2;
    }
    else if (instruction->attributes & ZYDIS_ATTRIB_HAS_REPNE)
    {
        *pp = 3;
    }

    *vvvv = ~instruction->vvvv;
    *rex = ZydisEncodeRexLowNibble(instruction, high_r);
}

/**
 * Emits `XOP` prefix.
 *
 * @param   instruction     A pointer to `ZydisEncoderInstruction` struct.
 * @param   buffer          A pointer to `ZydisEncoderBuffer` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEmitXop(ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer)
{
    ZyanU8 mmmmm, pp, vvvv, rex;
    ZydisEncodeVexCommons(instruction, &mmmmm, &pp, &vvvv, &rex, ZYAN_NULL);
    ZYAN_ASSERT(instruction->vector_length <= 1);
    const ZyanU8 b1 = (((~rex) & 0x07) << 5) | mmmmm;
    const ZyanU8 b2 = ((rex & 0x08) << 4) | ((vvvv & 0xF) << 3) | (instruction->vector_length << 2) | pp;
    ZYAN_CHECK(ZydisEmitByte(0x8F, buffer));
    ZYAN_CHECK(ZydisEmitByte(b1, buffer));
    ZYAN_CHECK(ZydisEmitByte(b2, buffer));
    return ZYAN_STATUS_SUCCESS;
}

/**
 * Emits `VEX` prefix.
 *
 * @param   instruction     A pointer to `ZydisEncoderInstruction` struct.
 * @param   buffer          A pointer to `ZydisEncoderBuffer` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEmitVex(ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer)
{
    ZyanU8 mmmmm, pp, vvvv, rex;
    ZydisEncodeVexCommons(instruction, &mmmmm, &pp, &vvvv, &rex, ZYAN_NULL);
    ZYAN_ASSERT(instruction->vector_length <= 1);
    if (mmmmm != 1 || (rex & 0x0B))
    {
        const ZyanU8 b1 = (((~rex) & 0x07) << 5) | mmmmm;
        const ZyanU8 b2 = ((rex & 0x08) << 4) |
                          ((vvvv & 0xF) << 3) |
                          (instruction->vector_length << 2) |
                          pp;
        ZYAN_CHECK(ZydisEmitByte(0xC4, buffer));
        ZYAN_CHECK(ZydisEmitByte(b1, buffer));
        ZYAN_CHECK(ZydisEmitByte(b2, buffer));
    }
    else
    {
        const ZyanU8 b1 = (((~rex) & 0x04) << 5) |
                          ((vvvv & 0xF) << 3) |
                          (instruction->vector_length << 2) |
                          pp;
        ZYAN_CHECK(ZydisEmitByte(0xC5, buffer));
        ZYAN_CHECK(ZydisEmitByte(b1, buffer));
    }

    return ZYAN_STATUS_SUCCESS;
}

/**
 * Encodes common parts of `EVEX` prefix.
 *
 * @param   instruction A pointer to `ZydisEncoderInstruction` struct.
 * @param   p0          A pointer to `ZyanU8` variable that will receive 2nd byte of `EVEX` prefix.
 * @param   p1          A pointer to `ZyanU8` variable that will receive 3rd byte of `EVEX` prefix.
 * @param   vvvvv       A pointer to `ZyanU8` variable that will receive `EVEX.vvvvv`.
 */

static void ZydisEncodeEvexCommons(ZydisEncoderInstruction *instruction, ZyanU8 *p0, ZyanU8 *p1,
    ZyanU8 *vvvvv)
{
    ZyanBool high_r;
    ZyanU8 mmmmm, pp, rex;
    ZydisEncodeVexCommons(instruction, &mmmmm, &pp, vvvvv, &rex, &high_r);
    *p0 = (((~rex) & 0x07) << 5) | mmmmm;
    if (!high_r)
    {
        *p0 |= 0x10;
    }
    *p1 = ((rex & 0x08) << 4) | ((*vvvvv & 0x0F) << 3) | 0x04 | pp;
}

/**
 * Emits `EVEX` prefix.
 *
 * @param   instruction     A pointer to `ZydisEncoderInstruction` struct.
 * @param   buffer          A pointer to `ZydisEncoderBuffer` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEmitEvex(ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer)
{
    ZyanU8 p0, p1, vvvvv;
    ZydisEncodeEvexCommons(instruction, &p0, &p1, &vvvvv);
    ZyanU8 p2 = (instruction->vector_length << 5) | ((vvvvv & 0x10) >> 1) | instruction->mask;
    if (instruction->zeroing)
    {
        p2 |= 0x80;
    }
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_EVEX_B)
    {
        p2 |= 0x10;
    }
    if (instruction->index & 0x10)
    {
        p2 &= 0xF7;
    }

    ZYAN_CHECK(ZydisEmitByte(0x62, buffer));
    ZYAN_CHECK(ZydisEmitByte(p0, buffer));
    ZYAN_CHECK(ZydisEmitByte(p1, buffer));
    ZYAN_CHECK(ZydisEmitByte(p2, buffer));
    return ZYAN_STATUS_SUCCESS;
}

/**
 * Emits `MVEX` prefix.
 *
 * @param   instruction     A pointer to `ZydisEncoderInstruction` struct.
 * @param   buffer          A pointer to `ZydisEncoderBuffer` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEmitMvex(ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer)
{
    ZyanU8 p0, p1, vvvvv;
    ZydisEncodeEvexCommons(instruction, &p0, &p1, &vvvvv);
    ZyanU8 p2 = (instruction->sss << 4) | ((vvvvv & 0x10) >> 1) | instruction->mask;
    if (instruction->eviction_hint)
    {
        p2 |= 0x80;
    }
    if (instruction->index & 0x10)
    {
        p2 &= 0xF7;
    }

    ZYAN_CHECK(ZydisEmitByte(0x62, buffer));
    ZYAN_CHECK(ZydisEmitByte(p0, buffer));
    ZYAN_CHECK(ZydisEmitByte(p1 & 0xFB, buffer));
    ZYAN_CHECK(ZydisEmitByte(p2, buffer));
    return ZYAN_STATUS_SUCCESS;
}

/**
 * Emits instruction as stream of bytes.
 *
 * @param   instruction     A pointer to `ZydisEncoderInstruction` struct.
 * @param   buffer          A pointer to `ZydisEncoderBuffer` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEmitInstruction(ZydisEncoderInstruction *instruction,
    ZydisEncoderBuffer *buffer)
{
    ZYAN_CHECK(ZydisEmitLegacyPrefixes(instruction, buffer));

    switch (instruction->encoding)
    {
    case ZYDIS_INSTRUCTION_ENCODING_LEGACY:
    case ZYDIS_INSTRUCTION_ENCODING_3DNOW:
        ZYAN_CHECK(ZydisEmitRex(instruction, buffer));
        break;
    case ZYDIS_INSTRUCTION_ENCODING_XOP:
        ZYAN_CHECK(ZydisEmitXop(instruction, buffer));
        break;
    case ZYDIS_INSTRUCTION_ENCODING_VEX:
        ZYAN_CHECK(ZydisEmitVex(instruction, buffer));
        break;
    case ZYDIS_INSTRUCTION_ENCODING_EVEX:
        ZYAN_CHECK(ZydisEmitEvex(instruction, buffer));
        break;
    case ZYDIS_INSTRUCTION_ENCODING_MVEX:
        ZYAN_CHECK(ZydisEmitMvex(instruction, buffer));
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    switch (instruction->opcode_map)
    {
    case ZYDIS_OPCODE_MAP_DEFAULT:
        break;
    case ZYDIS_OPCODE_MAP_0F:
        ZYAN_CHECK(ZydisEmitByte(0x0F, buffer));
        break;
    case ZYDIS_OPCODE_MAP_0F38:
        ZYAN_CHECK(ZydisEmitByte(0x0F, buffer));
        ZYAN_CHECK(ZydisEmitByte(0x38, buffer));
        break;
    case ZYDIS_OPCODE_MAP_0F3A:
        ZYAN_CHECK(ZydisEmitByte(0x0F, buffer));
        ZYAN_CHECK(ZydisEmitByte(0x3A, buffer));
        break;
    case ZYDIS_OPCODE_MAP_0F0F:
        ZYAN_CHECK(ZydisEmitByte(0x0F, buffer));
        ZYAN_CHECK(ZydisEmitByte(0x0F, buffer));
        break;
    default:
        ZYAN_UNREACHABLE;
    }
    if (instruction->encoding != ZYDIS_INSTRUCTION_ENCODING_3DNOW)
    {
        ZYAN_CHECK(ZydisEmitByte(instruction->opcode, buffer));
    }

    if (instruction->attributes & ZYDIS_ATTRIB_HAS_MODRM)
    {
        const ZyanU8 modrm = (instruction->mod << 6) |
                             ((instruction->reg & 7) << 3) |
                             (instruction->rm & 7);
        ZYAN_CHECK(ZydisEmitByte(modrm, buffer));
    }
    if (instruction->attributes & ZYDIS_ATTRIB_HAS_SIB)
    {
        const ZyanU8 sib = (instruction->scale << 6) |
                           ((instruction->index & 7) << 3) |
                           (instruction->base & 7);
        ZYAN_CHECK(ZydisEmitByte(sib, buffer));
    }
    if (instruction->disp_size)
    {
        ZYAN_CHECK(ZydisEmitUInt(instruction->disp, instruction->disp_size / 8, buffer));
    }
    if (instruction->imm_size)
    {
        ZYAN_CHECK(ZydisEmitUInt(instruction->imm, instruction->imm_size / 8, buffer));
    }
    if (instruction->encoding == ZYDIS_INSTRUCTION_ENCODING_3DNOW)
    {
        ZYAN_CHECK(ZydisEmitByte(instruction->opcode, buffer));
    }

    return ZYAN_STATUS_SUCCESS;
}

/**
 * Encodes register operand as fields inside `ZydisEncoderInstruction` structure.
 *
 * @param   user_op     Validated operand definition from `ZydisEncoderRequest` structure.
 * @param   def_op      Decoder's operand definition from instruction definition.
 * @param   instruction A pointer to `ZydisEncoderInstruction` struct.
 */

static void ZydisBuildRegisterOperand(const ZydisEncoderOperand *user_op,
    const ZydisOperandDefinition *def_op, ZydisEncoderInstruction *instruction)
{
    if (def_op->type == ZYDIS_SEMANTIC_OPTYPE_IMPLICIT_REG)
    {
        return;
    }

    ZyanU8 reg_id = 0;
    if (ZydisRegisterGetClass(user_op->reg.value) != ZYDIS_REGCLASS_GPR8)
    {
        reg_id = (ZyanU8)ZydisRegisterGetId(user_op->reg.value);
    }
    else
    {
        static const ZyanU8 reg8_lookup[] = {
            0, 1, 2, 3,                     // AL, CL, DL, BL
            4, 5, 6, 7,                     // AH, CH, DH, BH
            4, 5, 6, 7,                     // SPL, BPL, SIL, DIL
            8, 9, 10, 11, 12, 13, 14, 15,   // R8B-R15B
        };
        ZYAN_ASSERT(
            ((ZyanUSize)user_op->reg.value - ZYDIS_REGISTER_AL) < ZYAN_ARRAY_LENGTH(reg8_lookup));
        reg_id = reg8_lookup[user_op->reg.value - ZYDIS_REGISTER_AL];
        if (user_op->reg.value >= ZYDIS_REGISTER_SPL && user_op->reg.value <= ZYDIS_REGISTER_DIL)
        {
            instruction->attributes |= ZYDIS_ATTRIB_HAS_REX;
        }
    }

    switch (def_op->op.encoding)
    {
    case ZYDIS_OPERAND_ENCODING_MODRM_REG:
        instruction->attributes |= ZYDIS_ATTRIB_HAS_MODRM;
        instruction->reg = reg_id;
        break;
    case ZYDIS_OPERAND_ENCODING_MODRM_RM:
        instruction->attributes |= ZYDIS_ATTRIB_HAS_MODRM;
        instruction->rm = reg_id;
        break;
    case ZYDIS_OPERAND_ENCODING_OPCODE:
        instruction->opcode += reg_id & 7;
        instruction->rm = reg_id;
        break;
    case ZYDIS_OPERAND_ENCODING_NDSNDD:
        instruction->vvvv = reg_id;
        break;
    case ZYDIS_OPERAND_ENCODING_IS4:
        instruction->imm_size = 8;
        instruction->imm = reg_id << 4;
        break;
    case ZYDIS_OPERAND_ENCODING_MASK:
        instruction->mask = reg_id;
        break;
    default:
        ZYAN_UNREACHABLE;
    }
}

/**
 * Encodes memory operand as fields inside `ZydisEncoderInstruction` structure.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   user_op     Decoder's operand definition from instruction definition.
 * @param   instruction A pointer to `ZydisEncoderInstruction` struct.
 */

static void ZydisBuildMemoryOperand(ZydisEncoderInstructionMatch *match,
    const ZydisEncoderOperand *user_op, ZydisEncoderInstruction *instruction)
{
    instruction->attributes |= ZYDIS_ATTRIB_HAS_MODRM;
    instruction->disp = (ZyanU64)user_op->mem.displacement;
    if (match->easz == 16)
    {
        const ZyanI8 rm = ZydisGetRm16(user_op->mem.base, user_op->mem.index);
        if (rm != -1)
        {
            instruction->rm = (ZyanU8)rm;
            instruction->disp_size = match->disp_size;
            switch (instruction->disp_size)
            {
            case 0:
                if (rm == 6)
                {
                    instruction->disp_size = 8;
                    instruction->mod = 1;
                }
                break;
            case 8:
                instruction->mod = 1;
                break;
            case 16:
                instruction->mod = 2;
                break;
            default:
                ZYAN_UNREACHABLE;
            }
        }
        else
        {
            instruction->rm = 6;
            instruction->disp_size = 16;
        }
        return;
    }

    if (user_op->mem.index == ZYDIS_REGISTER_NONE)
    {
        if (user_op->mem.base == ZYDIS_REGISTER_NONE)
        {
            if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)
            {
                instruction->rm = 4;
                instruction->attributes |= ZYDIS_ATTRIB_HAS_SIB;
                instruction->base = 5;
                instruction->index = 4;
            }
            else
            {
                instruction->rm = 5;
            }
            instruction->disp_size = 32;
            return;
        }
        else if ((user_op->mem.base == ZYDIS_REGISTER_RIP) ||
                 (user_op->mem.base == ZYDIS_REGISTER_EIP))
        {
            instruction->rm = 5;
            instruction->disp_size = 32;
            return;
        }
    }

    const ZyanU8 reg_base_id = (ZyanU8)ZydisRegisterGetId(user_op->mem.base);
    const ZyanU8 reg_index_id = (ZyanU8)ZydisRegisterGetId(user_op->mem.index);
    instruction->disp_size = match->disp_size;
    switch (instruction->disp_size)
    {
    case 0:
        if (reg_base_id == 5 || reg_base_id == 13)
        {
            instruction->disp_size = 8;
            instruction->disp = 0;
            instruction->mod = 1;
        }
        break;
    case 8:
        instruction->mod = 1;
        break;
    case 16:
        instruction->disp_size = 32;
        ZYAN_FALLTHROUGH;
    case 32:
        instruction->mod = 2;
        break;
    default:
        ZYAN_UNREACHABLE;
    }
    if ((user_op->mem.index == ZYDIS_REGISTER_NONE) &&
        (reg_base_id != 4) &&
        (reg_base_id != 12) &&
        ((match->definition->modrm & 7) != 4))
    {
        instruction->rm = reg_base_id;
        return;
    }
    instruction->rm = 4;
    instruction->attributes |= ZYDIS_ATTRIB_HAS_SIB;
    if (reg_base_id != 0xFF)
    {
        instruction->base = reg_base_id;
    }
    else
    {
        instruction->base = 5;
        instruction->mod = 0;
        instruction->disp_size = 32;
    }
    if (reg_index_id != 0xFF)
    {
        instruction->index = reg_index_id;
    }
    else
    {
        instruction->index = 4;
    }
    switch (user_op->mem.scale)
    {
    case 0:
    case 1:
        break;
    case 2:
        instruction->scale = 1;
        break;
    case 4:
        instruction->scale = 2;
        break;
    case 8:
        instruction->scale = 3;
        break;
    default:
        ZYAN_UNREACHABLE;
    }
}

/**
 * Encodes instruction as emittable `ZydisEncoderInstruction` struct.
 *
 * @param   match       A pointer to `ZydisEncoderInstructionMatch` struct.
 * @param   instruction A pointer to `ZydisEncoderInstruction` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisBuildInstruction(ZydisEncoderInstructionMatch *match,
    ZydisEncoderInstruction *instruction)
{
    ZYAN_MEMSET(instruction, 0, sizeof(ZydisEncoderInstruction));
    instruction->attributes = match->attributes;
    instruction->encoding = match->definition->encoding;
    instruction->opcode_map = match->definition->opcode_map;
    instruction->opcode = match->definition->opcode;
    instruction->rex_w = match->definition->rex_w;
    instruction->mod = (match->definition->modrm >> 6) & 3;
    instruction->reg = (match->definition->modrm >> 3) & 7;
    instruction->rm = match->definition->modrm & 7;
    if (match->definition->modrm)
    {
        instruction->attributes |= ZYDIS_ATTRIB_HAS_MODRM;
    }

    switch (match->definition->vector_length)
    {
    case ZYDIS_VECTOR_LENGTH_INVALID:
    case ZYDIS_VECTOR_LENGTH_128:
        instruction->vector_length = 0;
        break;
    case ZYDIS_VECTOR_LENGTH_256:
        instruction->vector_length = 1;
        break;
    case ZYDIS_VECTOR_LENGTH_512:
        instruction->vector_length = 2;
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    if (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX)
    {
        const ZydisInstructionDefinitionEVEX *evex_def =
            (const ZydisInstructionDefinitionEVEX *)match->base_definition;
        if (evex_def->mask_override != ZYDIS_MASK_OVERRIDE_ZEROING)
        {
            instruction->zeroing = match->request->evex.zeroing_mask;
        }
        if ((match->request->evex.sae) ||
            (match->request->evex.broadcast != ZYDIS_BROADCAST_MODE_INVALID))
        {
            instruction->attributes |= ZYDIS_ATTRIB_HAS_EVEX_B;
        }
        if (match->request->evex.rounding != ZYDIS_ROUNDING_MODE_INVALID)
        {
            instruction->attributes |= ZYDIS_ATTRIB_HAS_EVEX_B;
            switch (match->request->evex.rounding)
            {
            case ZYDIS_ROUNDING_MODE_RN:
                instruction->vector_length = 0;
                break;
            case ZYDIS_ROUNDING_MODE_RD:
                instruction->vector_length = 1;
                break;
            case ZYDIS_ROUNDING_MODE_RU:
                instruction->vector_length = 2;
                break;
            case ZYDIS_ROUNDING_MODE_RZ:
                instruction->vector_length = 3;
                break;
            default:
                ZYAN_UNREACHABLE;
            }
        }
    }
    else if (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX)
    {
        instruction->sss |= ZydisEncodeMvexBroadcastMode(match->request->mvex.broadcast);
        instruction->sss |= ZydisEncodeMvexConversionMode(match->request->mvex.conversion);

        switch (match->request->mvex.rounding)
        {
        case ZYDIS_ROUNDING_MODE_INVALID:
            break;
        case ZYDIS_ROUNDING_MODE_RN:
        case ZYDIS_ROUNDING_MODE_RD:
        case ZYDIS_ROUNDING_MODE_RU:
        case ZYDIS_ROUNDING_MODE_RZ:
            instruction->sss |= match->request->mvex.rounding - ZYDIS_ROUNDING_MODE_RN;
            break;
        default:
            ZYAN_UNREACHABLE;
        }

        switch (match->request->mvex.swizzle)
        {
        case ZYDIS_SWIZZLE_MODE_INVALID:
            break;
        case ZYDIS_SWIZZLE_MODE_DCBA:
        case ZYDIS_SWIZZLE_MODE_CDAB:
        case ZYDIS_SWIZZLE_MODE_BADC:
        case ZYDIS_SWIZZLE_MODE_DACB:
        case ZYDIS_SWIZZLE_MODE_AAAA:
        case ZYDIS_SWIZZLE_MODE_BBBB:
        case ZYDIS_SWIZZLE_MODE_CCCC:
        case ZYDIS_SWIZZLE_MODE_DDDD:
            instruction->sss |= match->request->mvex.swizzle - ZYDIS_SWIZZLE_MODE_DCBA;
            break;
        default:
            ZYAN_UNREACHABLE;
        }

        if ((match->request->mvex.sae) ||
            (match->request->mvex.eviction_hint) ||
            (match->request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID))
        {
            instruction->eviction_hint = ZYAN_TRUE;
        }
        if (match->request->mvex.sae)
        {
            instruction->sss |= 4;
        }

        // Following instructions violate general `MVEX.EH` handling rules. In all other cases this
        // bit is used either as eviction hint (memory operands present) or to encode MVEX-specific
        // functionality (register forms). Instructions listed below use `MVEX.EH` to identify
        // different instructions with memory operands and don't treat it as eviction hint.
        switch (match->request->mnemonic)
        {
        case ZYDIS_MNEMONIC_VMOVNRAPD:
        case ZYDIS_MNEMONIC_VMOVNRAPS:
            instruction->eviction_hint = ZYAN_FALSE;
            break;
        case ZYDIS_MNEMONIC_VMOVNRNGOAPD:
        case ZYDIS_MNEMONIC_VMOVNRNGOAPS:
            instruction->eviction_hint = ZYAN_TRUE;
            break;
        default:
            break;
        }
    }

    switch (match->definition->mandatory_prefix)
    {
    case ZYDIS_MANDATORY_PREFIX_NONE:
        break;
    case ZYDIS_MANDATORY_PREFIX_66:
        instruction->attributes |= ZYDIS_ATTRIB_HAS_OPERANDSIZE;
        break;
    case ZYDIS_MANDATORY_PREFIX_F2:
        instruction->attributes |= ZYDIS_ATTRIB_HAS_REPNE;
        break;
    case ZYDIS_MANDATORY_PREFIX_F3:
        instruction->attributes |= ZYDIS_ATTRIB_HAS_REP;
        break;
    default:
        ZYAN_UNREACHABLE;
    }

    const ZyanU8 mode_width = ZydisGetMachineModeWidth(match->request->machine_mode);
    if (match->easz != mode_width)
    {
        instruction->attributes |= ZYDIS_ATTRIB_HAS_ADDRESSSIZE;
    }
    if ((match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) &&
        (match->base_definition->operand_size_map != ZYDIS_OPSIZE_MAP_FORCE64))
    {
        switch (match->eosz)
        {
        case 16:
            instruction->attributes |= ZYDIS_ATTRIB_HAS_OPERANDSIZE;
            break;
        case 32:
            break;
        case 64:
            instruction->rex_w =
                match->base_definition->operand_size_map != ZYDIS_OPSIZE_MAP_DEFAULT64;
            break;
        default:
            ZYAN_UNREACHABLE;
        }
    }
    else
    {
        if (match->eosz != mode_width)
        {
            instruction->attributes |= ZYDIS_ATTRIB_HAS_OPERANDSIZE;
        }
    }

    for (ZyanU8 i = 0; i < match->request->operand_count; ++i)
    {
        const ZydisEncoderOperand *user_op = &match->request->operands[i];
        const ZydisOperandDefinition *def_op = &match->operands[i];
        switch (user_op->type)
        {
        case ZYDIS_OPERAND_TYPE_REGISTER:
            ZydisBuildRegisterOperand(user_op, def_op, instruction);
            break;
        case ZYDIS_OPERAND_TYPE_MEMORY:
            if (def_op->type != ZYDIS_SEMANTIC_OPTYPE_MOFFS)
            {
                ZydisBuildMemoryOperand(match, user_op, instruction);
                if ((match->cd8_scale) &&
                    (instruction->disp_size == 8))
                {
                    instruction->disp >>= match->cd8_scale;
                }
            }
            else
            {
                instruction->disp_size = match->disp_size;
                instruction->disp = (ZyanU64)user_op->mem.displacement;
            }
            break;
        case ZYDIS_OPERAND_TYPE_POINTER:
            instruction->disp_size = match->disp_size;
            instruction->disp = user_op->ptr.offset;
            instruction->imm_size = match->imm_size;
            instruction->imm = user_op->ptr.segment;
            break;
        case ZYDIS_OPERAND_TYPE_IMMEDIATE:
            if (def_op->type == ZYDIS_SEMANTIC_OPTYPE_IMPLICIT_IMM1)
            {
                break;
            }
            if (def_op->op.encoding != ZYDIS_OPERAND_ENCODING_IS4)
            {
                if (instruction->imm_size)
                {
                    ZYAN_ASSERT(instruction->disp_size == 0);
                    instruction->disp_size = match->disp_size;
                    instruction->disp = instruction->imm;
                }
                instruction->imm_size = match->imm_size;
                instruction->imm = user_op->imm.u;
            }
            else
            {
                ZYAN_ASSERT(instruction->imm_size == 8);
                instruction->imm |= user_op->imm.u;
            }
            break;
        default:
            ZYAN_UNREACHABLE;
        }
    }

    return ZYAN_STATUS_SUCCESS;
}

/**
 * Performs a set of sanity checks that must be satisfied for every valid encoder request.
 *
 * @param   request A pointer to `ZydisEncoderRequest` struct.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEncoderCheckRequestSanity(const ZydisEncoderRequest *request)
{
    if (((ZyanUSize)request->machine_mode > ZYDIS_MACHINE_MODE_MAX_VALUE) ||
        ((ZyanUSize)request->allowed_encodings > ZYDIS_ENCODABLE_ENCODING_MAX_VALUE) ||
        ((ZyanUSize)request->mnemonic > ZYDIS_MNEMONIC_MAX_VALUE) ||
        ((ZyanUSize)request->branch_type > ZYDIS_BRANCH_TYPE_MAX_VALUE) ||
        ((ZyanUSize)request->branch_width > ZYDIS_BRANCH_WIDTH_MAX_VALUE) ||
        ((ZyanUSize)request->address_size_hint > ZYDIS_ADDRESS_SIZE_HINT_MAX_VALUE) ||
        ((ZyanUSize)request->operand_size_hint > ZYDIS_OPERAND_SIZE_HINT_MAX_VALUE) ||
        ((ZyanUSize)request->evex.broadcast > ZYDIS_BROADCAST_MODE_MAX_VALUE) ||
        ((ZyanUSize)request->evex.rounding > ZYDIS_ROUNDING_MODE_MAX_VALUE) ||
        ((ZyanUSize)request->mvex.broadcast > ZYDIS_BROADCAST_MODE_MAX_VALUE) ||
        ((ZyanUSize)request->mvex.conversion > ZYDIS_CONVERSION_MODE_MAX_VALUE) ||
        ((ZyanUSize)request->mvex.rounding > ZYDIS_ROUNDING_MODE_MAX_VALUE) ||
        ((ZyanUSize)request->mvex.swizzle > ZYDIS_SWIZZLE_MODE_MAX_VALUE) ||
        (request->operand_count > ZYDIS_ENCODER_MAX_OPERANDS) ||
        (request->mnemonic == ZYDIS_MNEMONIC_INVALID) ||
        (request->prefixes & ~ZYDIS_ENCODABLE_PREFIXES))
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT)
    {
        if ((request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) &&
            (request->prefixes & ZYDIS_LEGACY_SEGMENTS))
        {
            return ZYAN_STATUS_INVALID_ARGUMENT;
        }

        ZyanU8 seg_override_count = 0;
        if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_CS)
        {
            ++seg_override_count;
        }
        if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_SS)
        {
            ++seg_override_count;
        }
        if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_DS)
        {
            ++seg_override_count;
        }
        if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_ES)
        {
            ++seg_override_count;
        }
        if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_FS)
        {
            ++seg_override_count;
        }
        if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_GS)
        {
            ++seg_override_count;
        }
        if (seg_override_count != 1)
        {
            return ZYAN_STATUS_INVALID_ARGUMENT;
        }
    }
    ZyanU8 rep_family_count = 0;
    if (request->prefixes & ZYDIS_ATTRIB_HAS_REP)
    {
        ++rep_family_count;
    }
    if (request->prefixes & ZYDIS_ATTRIB_HAS_REPE)
    {
        ++rep_family_count;
    }
    if (request->prefixes & ZYDIS_ATTRIB_HAS_REPNE)
    {
        ++rep_family_count;
    }
    if (rep_family_count > 1)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if ((request->prefixes & ZYDIS_ATTRIB_HAS_XACQUIRE) &&
        (request->prefixes & ZYDIS_ATTRIB_HAS_XRELEASE))
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if ((request->prefixes & ZYDIS_ATTRIB_HAS_BRANCH_NOT_TAKEN) &&
        (request->prefixes & ZYDIS_ATTRIB_HAS_BRANCH_TAKEN))
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if ((request->prefixes & ZYDIS_ATTRIB_HAS_NOTRACK) &&
        (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT))
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    static const ZyanBool branch_lookup
        [ZYDIS_BRANCH_WIDTH_MAX_VALUE + 1][ZYDIS_BRANCH_TYPE_MAX_VALUE + 1] =
    {
        /* NONE */ { ZYAN_TRUE,  ZYAN_TRUE,  ZYAN_TRUE,  ZYAN_TRUE  },
        /* 8    */ { ZYAN_TRUE,  ZYAN_TRUE,  ZYAN_FALSE, ZYAN_FALSE },
        /* 16   */ { ZYAN_TRUE,  ZYAN_FALSE, ZYAN_TRUE,  ZYAN_TRUE  },
        /* 32   */ { ZYAN_TRUE,  ZYAN_FALSE, ZYAN_TRUE,  ZYAN_TRUE  },
        /* 64   */ { ZYAN_TRUE,  ZYAN_FALSE, ZYAN_TRUE,  ZYAN_TRUE  },
    };
    if (!branch_lookup[request->branch_width][request->branch_type])
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    if (request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)
    {
        if (request->address_size_hint == ZYDIS_ADDRESS_SIZE_HINT_16)
        {
            return ZYAN_STATUS_INVALID_ARGUMENT;
        }
    }
    else
    {
        if ((request->branch_width == ZYDIS_BRANCH_WIDTH_64) ||
            (request->address_size_hint == ZYDIS_ADDRESS_SIZE_HINT_64) ||
            (request->operand_size_hint == ZYDIS_OPERAND_SIZE_HINT_64))
        {
            return ZYAN_STATUS_INVALID_ARGUMENT;
        }
    }

    for (ZyanU8 i = 0; i < request->operand_count; ++i)
    {
        const ZydisEncoderOperand *op = &request->operands[i];
        if ((op->type == ZYDIS_OPERAND_TYPE_UNUSED) ||
            ((ZyanUSize)op->type > ZYDIS_OPERAND_TYPE_MAX_VALUE))
        {
            return ZYAN_STATUS_INVALID_ARGUMENT;
        }

        switch (op->type)
        {
        case ZYDIS_OPERAND_TYPE_REGISTER:
            if (op->reg.value > ZYDIS_REGISTER_MAX_VALUE)
            {
                return ZYAN_STATUS_INVALID_ARGUMENT;
            }
            break;
        case ZYDIS_OPERAND_TYPE_MEMORY:
            if (((ZyanUSize)op->mem.base > ZYDIS_REGISTER_MAX_VALUE) ||
                ((ZyanUSize)op->mem.index > ZYDIS_REGISTER_MAX_VALUE) ||
                !ZydisIsScaleValid(op->mem.scale))
            {
                return ZYAN_STATUS_INVALID_ARGUMENT;
            }
            break;
        case ZYDIS_OPERAND_TYPE_POINTER:
        case ZYDIS_OPERAND_TYPE_IMMEDIATE:
            break;
        default:
            return ZYAN_STATUS_INVALID_ARGUMENT;
        }
    }

    return ZYAN_STATUS_SUCCESS;
}

/**
 * Encodes instruction with semantics specified in encoder request structure.
 *
 * @param   request     A pointer to the `ZydisEncoderRequest` struct. Must be validated before
 *                      calling this function.
 * @param   buffer      A pointer to the output buffer receiving encoded instruction.
 * @param   length      A pointer to the variable containing length of the output buffer. Upon
 *                      successful return this variable receives length of the encoded instruction.
 * @param   instruction Internal state of the encoder.
 *
 * @return  A zyan status code.
 */

static ZyanStatus ZydisEncoderEncodeInstructionInternal(const ZydisEncoderRequest *request,
    void *buffer, ZyanUSize *length, ZydisEncoderInstruction *instruction)
{
    ZydisEncoderInstructionMatch match;
    ZYAN_CHECK(ZydisFindMatchingDefinition(request, &match));
    ZydisEncoderBuffer output;
    output.buffer = (ZyanU8 *)buffer;
    output.size = *length;
    output.offset = 0;
    ZYAN_CHECK(ZydisBuildInstruction(&match, instruction));
    ZYAN_CHECK(ZydisEmitInstruction(instruction, &output));
    *length = output.offset;
    return ZYAN_STATUS_SUCCESS;
}

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

ZYDIS_EXPORT ZyanStatus ZydisEncoderEncodeInstruction(const ZydisEncoderRequest *request,
    void *buffer, ZyanUSize *length)
{
    if (!request || !buffer || !length)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    ZYAN_CHECK(ZydisEncoderCheckRequestSanity(request));

    ZydisEncoderInstruction instruction;
    return ZydisEncoderEncodeInstructionInternal(request, buffer, length, &instruction);
}

ZYDIS_EXPORT ZyanStatus ZydisEncoderEncodeInstructionAbsolute(ZydisEncoderRequest *request,
    void *buffer, ZyanUSize *length, ZyanU64 runtime_address)
{
    if (!request || !buffer || !length)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    ZYAN_CHECK(ZydisEncoderCheckRequestSanity(request));

    const ZydisEncoderRelInfo *rel_info = ZydisGetRelInfo(request->mnemonic);
    ZydisEncoderOperand *op_rip_rel = ZYAN_NULL;
    ZyanBool adjusted_rel = ZYAN_FALSE;
    ZyanU64 absolute_address = 0;
    ZyanU8 mode_index = ZydisGetMachineModeWidth(request->machine_mode) >> 5;
    for (ZyanU8 i = 0; i < request->operand_count; ++i)
    {
        ZydisEncoderOperand *op = &request->operands[i];
        if ((op->type == ZYDIS_OPERAND_TYPE_IMMEDIATE) && rel_info)
        {
            if (adjusted_rel)
            {
                return ZYAN_STATUS_INVALID_ARGUMENT;
            }

            switch (rel_info->accepts_scaling_hints)
            {
            case ZYDIS_SIZE_HINT_NONE:
            case ZYDIS_SIZE_HINT_OSZ:
            {
                static const ZyanI8 asz_priority[3][3] =
                {
                    { 0, 1, 2 },
                    { 0, 2, 1 },
                    { 0, 2, -1 },
                };
                static const ZyanI8 osz_priority[3][3] =
                {
                    { 0, 1, 2 },
                    { 0, 2, 1 },
                    { 0, 2, 1 },
                };
                ZyanI8 forced_priority_row[3] = { -1, -1, -1 };
                ZyanI8 *priority_row = ZYAN_NULL;
                ZyanU8 extra_length = 0;
                ZyanU8 start_offset = 0;
                if (rel_info->accepts_scaling_hints == ZYDIS_SIZE_HINT_NONE)
                {
                    if ((request->branch_type == ZYDIS_BRANCH_TYPE_FAR) ||
                        (request->branch_width == ZYDIS_BRANCH_WIDTH_64))
                    {
                        return ZYAN_STATUS_INVALID_ARGUMENT;
                    }
                    if ((rel_info->accepts_branch_hints) &&
                        (request->prefixes & (ZYDIS_ATTRIB_HAS_BRANCH_NOT_TAKEN |
                                              ZYDIS_ATTRIB_HAS_BRANCH_TAKEN)))
                    {
                        extra_length += 1;
                    }
                    if ((rel_info->accepts_bound) && (request->prefixes & ZYDIS_ATTRIB_HAS_BND))
                    {
                        extra_length += 1;
                        // `BND` prefix is not accepted for short `JMP` (Intel SDM Vol. 1)
                        if ((request->mnemonic == ZYDIS_MNEMONIC_JMP) &&
                            (request->branch_type == ZYDIS_BRANCH_TYPE_NONE) &&
                            (request->branch_width == ZYDIS_BRANCH_WIDTH_NONE))
                        {
                            start_offset = 1;
                        }
                    }
                    if (request->branch_width == ZYDIS_BRANCH_WIDTH_NONE)
                    {
                        if (request->branch_type == ZYDIS_BRANCH_TYPE_NEAR)
                        {
                            start_offset = 1;
                        }
                        priority_row = (ZyanI8 *)&asz_priority[mode_index];
                    }
                    else
                    {
                        forced_priority_row[0] = (ZyanI8)(request->branch_width - 1);
                        priority_row = (ZyanI8 *)&forced_priority_row;
                    }
                }
                else
                {
                    if (request->operand_size_hint == ZYDIS_OPERAND_SIZE_HINT_NONE)
                    {
                        priority_row = (ZyanI8 *)&osz_priority[mode_index];
                    }
                    else
                    {
                        if (request->operand_size_hint == ZYDIS_OPERAND_SIZE_HINT_64)
                        {
                            extra_length = 1;
                            forced_priority_row[0] = 2;
                        }
                        else
                        {
                            forced_priority_row[0] = (ZyanI8)(request->operand_size_hint - 1);
                        }
                        priority_row = (ZyanI8 *)&forced_priority_row;
                    }
                }
                ZYAN_ASSERT(ZYAN_ARRAY_LENGTH(asz_priority[0]) ==
                            ZYAN_ARRAY_LENGTH(osz_priority[0]));
                for (ZyanU8 j = start_offset; j < ZYAN_ARRAY_LENGTH(asz_priority[0]); ++j)
                {
                    ZyanI8 size_index = priority_row[j];
                    if (size_index < 0)
                    {
                        break;
                    }
                    ZyanU8 base_size = rel_info->size[mode_index][size_index];
                    if (base_size == 0)
                    {
                        continue;
                    }
                    ZyanU8 predicted_size = base_size + extra_length;
                    if (runtime_address > ZYAN_UINT64_MAX - predicted_size + 1)
                    {
                        continue;
                    }
                    ZyanI64 rel = (ZyanI64)(op->imm.u - (runtime_address + predicted_size));
                    ZyanU8 rel_size = ZydisGetSignedImmSize(rel);
                    if (rel_size > (8 << size_index))
                    {
                        continue;
                    }
                    op->imm.s = rel;
                    adjusted_rel = ZYAN_TRUE;
                    if (rel_info->accepts_scaling_hints == ZYDIS_SIZE_HINT_NONE)
                    {
                        if (request->branch_width == ZYDIS_BRANCH_WIDTH_NONE)
                        {
                            request->branch_width =
                                (ZydisBranchWidth)(ZYDIS_BRANCH_WIDTH_8 + size_index);
                        }
                    }
                    else
                    {
                        if (request->operand_size_hint == ZYDIS_OPERAND_SIZE_HINT_NONE)
                        {
                            request->operand_size_hint =
                                (ZydisOperandSizeHint)(ZYDIS_OPERAND_SIZE_HINT_8 + size_index);
                        }
                    }
                    break;
                }
                break;
            }
            case ZYDIS_SIZE_HINT_ASZ:
            {
                static const ZyanI8 asz_prefix_lookup[3][ZYDIS_ADDRESS_SIZE_HINT_MAX_VALUE + 1] =
                {
                    { 0, 0, 1, -1 },
                    { 0, 1, 0, -1 },
                    { 0, -1, 1, 0 },
                };
                ZyanI8 extra_length = asz_prefix_lookup[mode_index][request->address_size_hint];
                if (extra_length < 0)
                {
                    return ZYAN_STATUS_INVALID_ARGUMENT;
                }
                ZyanU8 asz_index = (request->address_size_hint == ZYDIS_ADDRESS_SIZE_HINT_NONE)
                    ? mode_index
                    : ZydisGetAszFromHint(request->address_size_hint) >> 5;
                ZYAN_ASSERT((rel_info->size[asz_index][0] != 0) &&
                            (rel_info->size[asz_index][1] == 0) &&
                            (rel_info->size[asz_index][2] == 0) &&
                            !rel_info->accepts_branch_hints);
                ZyanU8 predicted_size = rel_info->size[asz_index][0] + extra_length;
                if (runtime_address > ZYAN_UINT64_MAX - predicted_size + 1)
                {
                    return ZYAN_STATUS_INVALID_ARGUMENT;
                }
                ZyanI64 rel = (ZyanI64)(op->imm.u - (runtime_address + predicted_size));
                ZyanU8 rel_size = ZydisGetSignedImmSize(rel);
                if (rel_size > 8)
                {
                    return ZYAN_STATUS_INVALID_ARGUMENT;
                }
                op->imm.s = rel;
                adjusted_rel = ZYAN_TRUE;
                break;
            }
            default:
                ZYAN_UNREACHABLE;
            }
            if (!adjusted_rel)
            {
                return ZYAN_STATUS_INVALID_ARGUMENT;
            }
        }
        else if ((op->type == ZYDIS_OPERAND_TYPE_MEMORY) &&
                 ((op->mem.base == ZYDIS_REGISTER_EIP) ||
                  (op->mem.base == ZYDIS_REGISTER_RIP)))
        {
            if (op_rip_rel)
            {
                return ZYAN_STATUS_INVALID_ARGUMENT;
            }

            absolute_address = op->mem.displacement;
            op->mem.displacement = 0;
            op_rip_rel = op;
        }
    }

    ZydisEncoderInstruction instruction;
    ZYAN_CHECK(ZydisEncoderEncodeInstructionInternal(request, buffer, length, &instruction));
    if (op_rip_rel)
    {
        ZyanUSize instruction_size = *length;
        if (runtime_address > ZYAN_UINT64_MAX - instruction_size + 1)
        {
            return ZYAN_STATUS_INVALID_ARGUMENT;
        }
        ZyanI64 rip_rel = (ZyanI64)(absolute_address - (runtime_address + instruction_size));
        if (ZydisGetSignedImmSize(rip_rel) > 32)
        {
            return ZYAN_STATUS_INVALID_ARGUMENT;
        }
        ZYAN_ASSERT(instruction.disp_size != 0);
        ZyanU8 disp_offset = (instruction.disp_size >> 3) + (instruction.imm_size >> 3);
        if (instruction.encoding == ZYDIS_INSTRUCTION_ENCODING_3DNOW)
        {
            disp_offset += 1;
        }
        ZYAN_ASSERT(instruction_size > disp_offset);
        ZYAN_MEMCPY((ZyanU8 *)buffer + instruction_size - disp_offset, &rip_rel, sizeof(ZyanI32));
        op_rip_rel->mem.displacement = rip_rel;
    }

    return ZYAN_STATUS_SUCCESS;
}

ZYDIS_EXPORT ZyanStatus ZydisEncoderDecodedInstructionToEncoderRequest(
    const ZydisDecodedInstruction *instruction, const ZydisDecodedOperand* operands, 
    ZyanU8 operand_count, ZydisEncoderRequest *request)
{
    if (!instruction || !request || (operand_count && !operands))
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    ZYAN_MEMSET(request, 0, sizeof(ZydisEncoderRequest));
    request->machine_mode = instruction->machine_mode;
    request->mnemonic = instruction->mnemonic;
    request->prefixes = instruction->attributes & ZYDIS_ENCODABLE_PREFIXES;
    request->branch_type = instruction->meta.branch_type;
    if (!(instruction->attributes & ZYDIS_ATTRIB_ACCEPTS_SEGMENT))
    {
        request->prefixes &= ~ZYDIS_ATTRIB_HAS_SEGMENT;
    }

    switch (instruction->address_width)
    {
    case 16:
        request->address_size_hint = ZYDIS_ADDRESS_SIZE_HINT_16;
        break;
    case 32:
        request->address_size_hint = ZYDIS_ADDRESS_SIZE_HINT_32;
        break;
    case 64:
        request->address_size_hint = ZYDIS_ADDRESS_SIZE_HINT_64;
        break;
    default:
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    switch (instruction->operand_width)
    {
    case 8:
        request->operand_size_hint = ZYDIS_OPERAND_SIZE_HINT_8;
        break;
    case 16:
        request->operand_size_hint = ZYDIS_OPERAND_SIZE_HINT_16;
        break;
    case 32:
        request->operand_size_hint = ZYDIS_OPERAND_SIZE_HINT_32;
        break;
    case 64:
        request->operand_size_hint = ZYDIS_OPERAND_SIZE_HINT_64;
        break;
    default:
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    switch (request->branch_type)
    {
    case ZYDIS_BRANCH_TYPE_NONE:
        request->branch_width = ZYDIS_BRANCH_WIDTH_NONE;
        break;
    case ZYDIS_BRANCH_TYPE_SHORT:
        request->branch_width = ZYDIS_BRANCH_WIDTH_8;
        break;
    case ZYDIS_BRANCH_TYPE_NEAR:
    case ZYDIS_BRANCH_TYPE_FAR:
        switch (instruction->operand_width)
        {
        case 16:
            request->branch_width = ZYDIS_BRANCH_WIDTH_16;
            break;
        case 32:
            request->branch_width = ZYDIS_BRANCH_WIDTH_32;
            break;
        case 64:
            request->branch_width = ZYDIS_BRANCH_WIDTH_64;
            break;
        default:
            ZYAN_UNREACHABLE;
        }
        break;
    default:
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    switch (instruction->encoding)
    {
    case ZYDIS_INSTRUCTION_ENCODING_LEGACY:
    case ZYDIS_INSTRUCTION_ENCODING_3DNOW:
    case ZYDIS_INSTRUCTION_ENCODING_XOP:
    case ZYDIS_INSTRUCTION_ENCODING_VEX:
        break;
    case ZYDIS_INSTRUCTION_ENCODING_EVEX:
        request->evex.broadcast = !instruction->avx.broadcast.is_static ?
            instruction->avx.broadcast.mode : ZYDIS_BROADCAST_MODE_INVALID;
        request->evex.rounding = instruction->avx.rounding.mode;
        request->evex.sae = instruction->avx.has_sae;
        request->evex.zeroing_mask = (instruction->avx.mask.mode == ZYDIS_MASK_MODE_ZEROING ||
            instruction->avx.mask.mode == ZYDIS_MASK_MODE_CONTROL_ZEROING) &&
            (instruction->raw.evex.z) ? ZYAN_TRUE : ZYAN_FALSE;
        break;
    case ZYDIS_INSTRUCTION_ENCODING_MVEX:
        request->mvex.broadcast = !instruction->avx.broadcast.is_static ?
            instruction->avx.broadcast.mode : ZYDIS_BROADCAST_MODE_INVALID;
        request->mvex.conversion = instruction->avx.conversion.mode;
        request->mvex.rounding = instruction->avx.rounding.mode;
        request->mvex.swizzle = instruction->avx.swizzle.mode;
        request->mvex.sae = instruction->avx.has_sae;
        request->mvex.eviction_hint = instruction->avx.has_eviction_hint;
        break;
    default:
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    request->allowed_encodings = 1 << instruction->encoding;

    if ((operand_count > ZYDIS_ENCODER_MAX_OPERANDS) || 
        (operand_count > instruction->operand_count_visible))
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    request->operand_count = operand_count;
    for (ZyanU8 i = 0; i < operand_count; ++i)
    {
        const ZydisDecodedOperand *dec_op = &operands[i];
        ZydisEncoderOperand *enc_op = &request->operands[i];

        enc_op->type = dec_op->type;
        switch (dec_op->type)
        {
        case ZYDIS_OPERAND_TYPE_REGISTER:
            enc_op->reg.value = dec_op->reg.value;
            enc_op->reg.is4 = dec_op->encoding == ZYDIS_OPERAND_ENCODING_IS4;
            break;
        case ZYDIS_OPERAND_TYPE_MEMORY:
            enc_op->mem.base = dec_op->mem.base;
            enc_op->mem.index = dec_op->mem.index;
            enc_op->mem.scale = dec_op->mem.type != ZYDIS_MEMOP_TYPE_MIB ? dec_op->mem.scale : 0;
            if (dec_op->mem.disp.has_displacement)
            {
                enc_op->mem.displacement = dec_op->mem.disp.value;
            }
            enc_op->mem.size = dec_op->size / 8;
            break;
        case ZYDIS_OPERAND_TYPE_POINTER:
            enc_op->ptr.segment = dec_op->ptr.segment;
            enc_op->ptr.offset = dec_op->ptr.offset;
            break;
        case ZYDIS_OPERAND_TYPE_IMMEDIATE:
            enc_op->imm.u = dec_op->imm.value.u;
            // `XBEGIN` is an ISA-wide unique instruction because it's not a branching instruction
            // but it has a relative operand which behaves differently from all other relatives
            // (no truncating behavior in 16-bit mode). Encoder treats it as non-branching
            // instruction that scales with hidden operand size.
            if ((dec_op->imm.is_relative) &&
                (instruction->mnemonic != ZYDIS_MNEMONIC_XBEGIN))
            {
                switch (instruction->raw.imm->size)
                {
                case 8:
                    request->branch_width = ZYDIS_BRANCH_WIDTH_8;
                    break;
                case 16:
                    request->branch_width = ZYDIS_BRANCH_WIDTH_16;
                    break;
                case 32:
                    request->branch_width = ZYDIS_BRANCH_WIDTH_32;
                    break;
                default:
                    return ZYAN_STATUS_INVALID_ARGUMENT;
                }
            }
            break;
        default:
            return ZYAN_STATUS_INVALID_ARGUMENT;
        }
    }

    return ZYAN_STATUS_SUCCESS;
}

ZYDIS_EXPORT ZyanStatus ZydisEncoderNopFill(void *buffer, ZyanUSize length)
{
    if (!buffer)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Intel SDM Vol. 2B "Recommended Multi-Byte Sequence of NOP Instruction"
    static const ZyanU8 nops[9][9] =
    {
        { 0x90 },
        { 0x66, 0x90 },
        { 0x0F, 0x1F, 0x00 },
        { 0x0F, 0x1F, 0x40, 0x00 },
        { 0x0F, 0x1F, 0x44, 0x00, 0x00 },
        { 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00 },
        { 0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00 },
        { 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 },
        { 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 },
    };

    ZyanU8 *output = (ZyanU8 *)buffer;
    while (length)
    {
        ZyanUSize nop_size = (length > 9) ? 9 : length;
        ZYAN_MEMCPY(output, nops[nop_size - 1], nop_size);
        output += nop_size;
        length -= nop_size;
    }

    return ZYAN_STATUS_SUCCESS;
}

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

Messung V0.5 in Prozent
C=97 H=99 G=97

¤ Dauer der Verarbeitung: 0.86 Sekunden  (vorverarbeitet am  2026-05-09) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.