/*
  Copyright (c) 2015 DataLab, s.l. <http://www.datalab.es>
  This file is part of GlusterFS.

  This file is licensed to you under your choice of the GNU Lesser
  General Public License, version 3 or any later version (LGPLv3 or
  later), or the GNU General Public License, version 2 (GPLv2), in all
  cases as published by the Free Software Foundation.
*/

#include <inttypes.h>
#include <string.h>
#include <errno.h>

#include "ec-code-intel.h"

static void
ec_code_intel_init(ec_code_intel_t *intel)
{
    memset(intel, 0, sizeof(ec_code_intel_t));
}

static void
ec_code_intel_prefix(ec_code_intel_t *intel, uint8_t prefix)
{
    intel->prefix.data[intel->prefix.bytes++] = prefix;
}

static void
ec_code_intel_rex(ec_code_intel_t *intel, gf_boolean_t w)
{
    gf_boolean_t present = _gf_false;

    if (w) {
        intel->rex.w = 1;
        present = _gf_true;
    }
    if (intel->modrm.present) {
        if (intel->modrm.reg > 7) {
            intel->modrm.reg &= 7;
            intel->rex.r = 1;
            present = _gf_true;
        }
        if (intel->sib.present) {
            if (intel->sib.index > 7) {
                intel->sib.index &= 7;
                intel->rex.x = 1;
                present = _gf_true;
            }
            if (intel->sib.base > 7) {
                intel->sib.base &= 7;
                intel->rex.b = 1;
                present = _gf_true;
            }
        } else if (intel->modrm.rm > 7) {
            intel->modrm.rm &= 7;
            intel->rex.b = 1;
            present = _gf_true;
        }
    } else if (intel->reg > 7) {
        intel->reg &= 7;
        intel->rex.b = 1;
        present = _gf_true;
    }
    intel->rex.present = present;
}

static void
ec_code_intel_vex(ec_code_intel_t *intel, gf_boolean_t w, gf_boolean_t l,
                  ec_code_vex_opcode_t opcode, ec_code_vex_prefix_t prefix,
                  uint32_t reg)
{
    ec_code_intel_rex(intel, w);
    if (((intel->rex.w == 1) || (intel->rex.x == 0) || (intel->rex.b == 0)) ||
        ((opcode != VEX_OPCODE_NONE) && (opcode != VEX_OPCODE_0F))) {
        intel->rex.present = _gf_false;

        intel->vex.bytes = 3;
        intel->vex.data[0] = 0xC4;
        intel->vex.data[1] = ((intel->rex.r << 7) | (intel->rex.x << 6) |
                              (intel->rex.b << 5) | opcode) ^
                             0xE0;
        intel->vex.data[2] = (intel->rex.w << 7) | ((~reg & 0x0F) << 3) |
                             (l ? 0x04 : 0x00) | prefix;
    } else {
        intel->vex.bytes = 2;
        intel->vex.data[0] = 0xC5;
        intel->vex.data[1] = (intel->rex.r << 7) | ((~reg & 0x0F) << 3) |
                             (l ? 0x04 : 0x00) | prefix;
    }
}

static void
ec_code_intel_modrm_reg(ec_code_intel_t *intel, uint32_t rm, uint32_t reg)
{
    intel->modrm.present = _gf_true;
    intel->modrm.mod = 3;
    intel->modrm.rm = rm;
    intel->modrm.reg = reg;
}

static void
ec_code_intel_modrm_mem(ec_code_intel_t *intel, uint32_t reg,
                        ec_code_intel_reg_t base, ec_code_intel_reg_t index,
                        uint32_t scale, int32_t offset)
{
    if (index == REG_SP) {
        intel->invalid = _gf_true;
        return;
    }
    if ((index != REG_NULL) && (scale != 1) && (scale != 2) && (scale != 4) &&
        (scale != 8)) {
        intel->invalid = _gf_true;
        return;
    }
    scale >>= 1;
    if (scale == 4) {
        scale = 3;
    }

    intel->modrm.present = _gf_true;
    intel->modrm.reg = reg;

    intel->offset.value = offset;
    if ((offset == 0) && (base != REG_BP)) {
        intel->modrm.mod = 0;
        intel->offset.bytes = 0;
    } else if ((offset >= -128) && (offset <= 127)) {
        intel->modrm.mod = 1;
        intel->offset.bytes = 1;
    } else {
        intel->modrm.mod = 2;
        intel->offset.bytes = 4;
    }

    intel->modrm.rm = base;
    if ((index != REG_NULL) || (base == REG_SP)) {
        intel->modrm.rm = 4;
        intel->sib.present = _gf_true;
        intel->sib.index = index;
        if (index == REG_NULL) {
            intel->sib.index = 4;
        }
        intel->sib.scale = scale;
        intel->sib.base = base;
        if (base == REG_NULL) {
            intel->sib.base = 5;
            intel->modrm.mod = 0;
            intel->offset.bytes = 4;
        }
    } else if (base == REG_NULL) {
        intel->modrm.mod = 0;
        intel->modrm.rm = 5;
        intel->offset.bytes = 4;
    }
}

static void
ec_code_intel_op_1(ec_code_intel_t *intel, uint8_t opcode, uint32_t reg)
{
    intel->reg = reg;
    intel->opcode.bytes = 1;
    intel->opcode.data[0] = opcode;
}

static void
ec_code_intel_op_2(ec_code_intel_t *intel, uint8_t opcode1, uint8_t opcode2,
                   uint32_t reg)
{
    intel->reg = reg;
    intel->opcode.bytes = 2;
    intel->opcode.data[0] = opcode1;
    intel->opcode.data[1] = opcode2;
}

static void
ec_code_intel_immediate_1(ec_code_intel_t *intel, uint32_t value)
{
    intel->immediate.bytes = 1;
    intel->immediate.value = value;
}

static void
ec_code_intel_immediate_2(ec_code_intel_t *intel, uint32_t value)
{
    intel->immediate.bytes = 2;
    intel->immediate.value = value;
}

static void
ec_code_intel_immediate_4(ec_code_intel_t *intel, uint32_t value)
{
    intel->immediate.bytes = 4;
    intel->immediate.value = value;
}

static void
ec_code_intel_emit(ec_code_builder_t *builder, ec_code_intel_t *intel)
{
    uint8_t insn[15];
    uint32_t i, count;

    if (intel->invalid) {
        ec_code_error(builder, EINVAL);
        return;
    }

    count = 0;
    for (i = 0; i < intel->prefix.bytes; i++) {
        insn[count++] = intel->prefix.data[i];
    }
    for (i = 0; i < intel->vex.bytes; i++) {
        insn[count++] = intel->vex.data[i];
    }
    if (intel->rex.present) {
        insn[count++] = 0x40 | (intel->rex.w << 3) | (intel->rex.r << 2) |
                        (intel->rex.x << 1) | (intel->rex.b << 0);
    }
    for (i = 0; i < intel->opcode.bytes; i++) {
        insn[count++] = intel->opcode.data[i];
    }
    if (intel->modrm.present) {
        insn[count++] = (intel->modrm.mod << 6) | (intel->modrm.reg << 3) |
                        (intel->modrm.rm << 0);
        if (intel->sib.present) {
            insn[count++] = (intel->sib.scale << 6) | (intel->sib.index << 3) |
                            (intel->sib.base << 0);
        }
    }
    for (i = 0; i < intel->offset.bytes; i++) {
        insn[count++] = intel->offset.data[i];
    }
    for (i = 0; i < intel->immediate.bytes; i++) {
        insn[count++] = intel->immediate.data[i];
    }

    ec_code_emit(builder, insn, count);
}

void
ec_code_intel_op_push_r(ec_code_builder_t *builder, ec_code_intel_reg_t reg)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_op_1(&intel, 0x50 | (reg & 7), reg);
    ec_code_intel_rex(&intel, _gf_false);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_pop_r(ec_code_builder_t *builder, ec_code_intel_reg_t reg)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_op_1(&intel, 0x58 | (reg & 7), reg);
    ec_code_intel_rex(&intel, _gf_false);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_ret(ec_code_builder_t *builder, uint32_t size)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    if (size == 0) {
        ec_code_intel_op_1(&intel, 0xC3, 0);
    } else {
        ec_code_intel_immediate_2(&intel, size);
        ec_code_intel_op_1(&intel, 0xC2, 0);
    }
    ec_code_intel_rex(&intel, _gf_false);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_mov_r2r(ec_code_builder_t *builder, ec_code_intel_reg_t src,
                         ec_code_intel_reg_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_reg(&intel, dst, src);
    ec_code_intel_op_1(&intel, 0x89, 0);
    ec_code_intel_rex(&intel, _gf_true);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_mov_r2m(ec_code_builder_t *builder, ec_code_intel_reg_t src,
                         ec_code_intel_reg_t base, ec_code_intel_reg_t index,
                         uint32_t scale, int32_t offset)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_mem(&intel, src, base, index, scale, offset);
    ec_code_intel_op_1(&intel, 0x89, 0);
    ec_code_intel_rex(&intel, _gf_true);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_mov_m2r(ec_code_builder_t *builder, ec_code_intel_reg_t base,
                         ec_code_intel_reg_t index, uint32_t scale,
                         int32_t offset, ec_code_intel_reg_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_mem(&intel, dst, base, index, scale, offset);
    ec_code_intel_op_1(&intel, 0x8B, 0);
    ec_code_intel_rex(&intel, _gf_true);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_xor_r2r(ec_code_builder_t *builder, ec_code_intel_reg_t src,
                         ec_code_intel_reg_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_reg(&intel, dst, src);
    ec_code_intel_op_1(&intel, 0x31, 0);
    ec_code_intel_rex(&intel, _gf_true);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_xor_m2r(ec_code_builder_t *builder, ec_code_intel_reg_t base,
                         ec_code_intel_reg_t index, uint32_t scale,
                         int32_t offset, ec_code_intel_reg_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_mem(&intel, dst, base, index, scale, offset);
    ec_code_intel_op_1(&intel, 0x33, 0);
    ec_code_intel_rex(&intel, _gf_true);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_add_i2r(ec_code_builder_t *builder, int32_t value,
                         ec_code_intel_reg_t reg)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    if ((value >= -128) && (value < 128)) {
        ec_code_intel_modrm_reg(&intel, reg, 0);
        ec_code_intel_op_1(&intel, 0x83, 0);
        ec_code_intel_immediate_1(&intel, value);
    } else {
        if (reg == REG_AX) {
            ec_code_intel_op_1(&intel, 0x05, reg);
        } else {
            ec_code_intel_modrm_reg(&intel, reg, 0);
            ec_code_intel_op_1(&intel, 0x81, 0);
        }
        ec_code_intel_immediate_4(&intel, value);
    }
    ec_code_intel_rex(&intel, _gf_true);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_test_i2r(ec_code_builder_t *builder, uint32_t value,
                          ec_code_intel_reg_t reg)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    if (reg == REG_AX) {
        ec_code_intel_op_1(&intel, 0xA9, reg);
    } else {
        ec_code_intel_modrm_reg(&intel, reg, 0);
        ec_code_intel_op_1(&intel, 0xF7, 0);
    }
    ec_code_intel_immediate_4(&intel, value);
    ec_code_intel_rex(&intel, _gf_true);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_jne(ec_code_builder_t *builder, uint32_t address)
{
    ec_code_intel_t intel;
    int32_t rel;

    ec_code_intel_init(&intel);

    rel = address - builder->address - 2;
    if ((rel >= -128) && (rel < 128)) {
        ec_code_intel_op_1(&intel, 0x75, 0);
        ec_code_intel_immediate_1(&intel, rel);
    } else {
        rel -= 4;
        ec_code_intel_op_2(&intel, 0x0F, 0x85, 0);
        ec_code_intel_immediate_4(&intel, rel);
    }
    ec_code_intel_rex(&intel, _gf_false);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_mov_sse2sse(ec_code_builder_t *builder, uint32_t src,
                             uint32_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_prefix(&intel, 0x66);
    ec_code_intel_modrm_reg(&intel, src, dst);
    ec_code_intel_op_2(&intel, 0x0F, 0x6F, 0);
    ec_code_intel_rex(&intel, _gf_false);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_mov_sse2m(ec_code_builder_t *builder, uint32_t src,
                           ec_code_intel_reg_t base, ec_code_intel_reg_t index,
                           uint32_t scale, int32_t offset)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_prefix(&intel, 0x66);
    ec_code_intel_modrm_mem(&intel, src, base, index, scale, offset);
    ec_code_intel_op_2(&intel, 0x0F, 0x7F, 0);
    ec_code_intel_rex(&intel, _gf_false);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_mov_m2sse(ec_code_builder_t *builder, ec_code_intel_reg_t base,
                           ec_code_intel_reg_t index, uint32_t scale,
                           int32_t offset, uint32_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_prefix(&intel, 0x66);
    ec_code_intel_modrm_mem(&intel, dst, base, index, scale, offset);
    ec_code_intel_op_2(&intel, 0x0F, 0x6F, 0);
    ec_code_intel_rex(&intel, _gf_false);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_xor_sse2sse(ec_code_builder_t *builder, uint32_t src,
                             uint32_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_prefix(&intel, 0x66);
    ec_code_intel_modrm_reg(&intel, src, dst);
    ec_code_intel_op_2(&intel, 0x0F, 0xEF, 0);
    ec_code_intel_rex(&intel, _gf_false);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_xor_m2sse(ec_code_builder_t *builder, ec_code_intel_reg_t base,
                           ec_code_intel_reg_t index, uint32_t scale,
                           int32_t offset, uint32_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_prefix(&intel, 0x66);
    ec_code_intel_modrm_mem(&intel, dst, base, index, scale, offset);
    ec_code_intel_op_2(&intel, 0x0F, 0xEF, 0);
    ec_code_intel_rex(&intel, _gf_false);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_mov_avx2avx(ec_code_builder_t *builder, uint32_t src,
                             uint32_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_reg(&intel, src, dst);
    ec_code_intel_op_1(&intel, 0x6F, 0);
    ec_code_intel_vex(&intel, _gf_false, _gf_true, VEX_OPCODE_0F, VEX_PREFIX_66,
                      VEX_REG_NONE);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_mov_avx2m(ec_code_builder_t *builder, uint32_t src,
                           ec_code_intel_reg_t base, ec_code_intel_reg_t index,
                           uint32_t scale, int32_t offset)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_mem(&intel, src, base, index, scale, offset);
    ec_code_intel_op_1(&intel, 0x7F, 0);
    ec_code_intel_vex(&intel, _gf_false, _gf_true, VEX_OPCODE_0F, VEX_PREFIX_66,
                      VEX_REG_NONE);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_mov_m2avx(ec_code_builder_t *builder, ec_code_intel_reg_t base,
                           ec_code_intel_reg_t index, uint32_t scale,
                           int32_t offset, uint32_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_mem(&intel, dst, base, index, scale, offset);
    ec_code_intel_op_1(&intel, 0x6F, 0);
    ec_code_intel_vex(&intel, _gf_false, _gf_true, VEX_OPCODE_0F, VEX_PREFIX_66,
                      VEX_REG_NONE);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_xor_avx2avx(ec_code_builder_t *builder, uint32_t src,
                             uint32_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_reg(&intel, src, dst);
    ec_code_intel_op_1(&intel, 0xEF, 0);
    ec_code_intel_vex(&intel, _gf_false, _gf_true, VEX_OPCODE_0F, VEX_PREFIX_66,
                      dst);

    ec_code_intel_emit(builder, &intel);
}

void
ec_code_intel_op_xor_m2avx(ec_code_builder_t *builder, ec_code_intel_reg_t base,
                           ec_code_intel_reg_t index, uint32_t scale,
                           int32_t offset, uint32_t dst)
{
    ec_code_intel_t intel;

    ec_code_intel_init(&intel);

    ec_code_intel_modrm_mem(&intel, dst, base, index, scale, offset);
    ec_code_intel_op_1(&intel, 0xEF, 0);
    ec_code_intel_vex(&intel, _gf_false, _gf_true, VEX_OPCODE_0F, VEX_PREFIX_66,
                      dst);

    ec_code_intel_emit(builder, &intel);
}
