blob: 4ab3f331a3cb269d196137e1a868c4e72d9a39f7 [file] [edit]
#include "fadec-enc.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __GNUC__
#define LIKELY(x) __builtin_expect((x), 1)
#define UNLIKELY(x) __builtin_expect((x), 0)
#else
#define LIKELY(x) (x)
#define UNLIKELY(x) (x)
#endif
#define OPC_66 0x80000
#define OPC_F2 0x100000
#define OPC_F3 0x200000
#define OPC_REXW 0x400000
#define OPC_LOCK 0x800000
#define OPC_VEXL0 0x1000000
#define OPC_VEXL1 0x1800000
#define OPC_EVEXL0 0x2000000
#define OPC_EVEXL1 0x2800000
#define OPC_EVEXL2 0x3000000
#define OPC_EVEXL3 0x3800000
#define OPC_EVEXB 0x4000000
#define OPC_VSIB 0x8000000
#define OPC_67 FE_ADDR32
#define OPC_SEG_MSK 0xe0000000
#define OPC_JMPL FE_JMPL
#define OPC_MASK_MSK 0xe00000000
#define OPC_EVEXZ 0x1000000000
#define OPC_USER_MSK (OPC_67 | OPC_SEG_MSK | OPC_MASK_MSK)
#define OPC_FORCE_SIB 0x2000000000
#define OPC_DOWNGRADE_VEX 0x4000000000
#define OPC_DOWNGRADE_VEX_FLIPW 0x40000000000
#define OPC_EVEX_DISP8SCALE 0x38000000000
#define OPC_GPH_OP0 0x200000000000
#define OPC_GPH_OP1 0x400000000000
#define EPFX_REX_MSK 0x43f
#define EPFX_REX 0x20
#define EPFX_EVEX 0x40
#define EPFX_REXR 0x10
#define EPFX_REXX 0x08
#define EPFX_REXB 0x04
#define EPFX_REXR4 0x02
#define EPFX_REXB4 0x01
#define EPFX_REXX4 0x400
#define EPFX_VVVV_IDX 11
static bool op_mem(FeOp op) { return op < 0; }
static bool op_reg(FeOp op) { return op >= 0; }
static bool op_reg_gpl(FeOp op) { return (op & ~0x1f) == 0x100; }
static bool op_reg_gph(FeOp op) { return (op & ~0x3) == 0x204; }
static bool op_reg_xmm(FeOp op) { return (op & ~0x1f) == 0x600; }
static int64_t op_mem_offset(FeOp op) { return (int32_t)op; }
static unsigned op_mem_base(FeOp op) { return (op >> 32) & 0xfff; }
static unsigned op_mem_idx(FeOp op) { return (op >> 44) & 0xfff; }
static unsigned op_mem_scale(FeOp op) { return (op >> 56) & 0xf; }
static unsigned op_reg_idx(FeOp op) { return op & 0xff; }
static bool op_imm_n(FeOp imm, unsigned immsz) {
if (immsz == 0 && !imm)
return true;
if (immsz == 1 && (int8_t)imm == imm)
return true;
if (immsz == 2 && (int16_t)imm == imm)
return true;
if (immsz == 3 && (imm & 0xffffff) == imm)
return true;
if (immsz == 4 && (int32_t)imm == imm)
return true;
if (immsz == 8 && (int64_t)imm == imm)
return true;
return false;
}
static unsigned opc_size(uint64_t opc, uint64_t epfx) {
unsigned res = 1;
if (UNLIKELY(opc & OPC_EVEXL0)) {
res += 4;
} else if (UNLIKELY(opc & OPC_VEXL0)) {
if (opc & (OPC_REXW | 0x20000) || epfx & (EPFX_REXX | EPFX_REXB))
res += 3;
else
res += 2;
} else {
res += !!(opc & OPC_LOCK);
res += !!(opc & OPC_66);
res += !!(opc & (OPC_F2 | OPC_F3));
res += !!(opc & OPC_REXW || epfx & EPFX_REX_MSK);
res += !!(opc & 0x30000);
res += !!(opc & 0x20000);
}
res += !!(opc & OPC_SEG_MSK);
res += !!(opc & OPC_67);
res += !!(opc & 0x8000);
return res;
}
static int enc_opc(uint8_t** restrict buf, uint64_t opc, uint64_t epfx) {
if (opc & OPC_SEG_MSK)
*(*buf)++ = (0x65643e362e2600 >> (8 * ((opc >> 29) & 7))) & 0xff;
if (opc & OPC_67)
*(*buf)++ = 0x67;
if (opc & OPC_EVEXL0) {
*(*buf)++ = 0x62;
unsigned b1 = opc >> 16 & 7;
b1 |= !(epfx & EPFX_REXR) ? 0x80 : 0;
b1 |= !(epfx & EPFX_REXX) ? 0x40 : 0;
b1 |= !(epfx & EPFX_REXB) ? 0x20 : 0;
b1 |= !(epfx & EPFX_REXR4) ? 0x10 : 0;
b1 |= (epfx & EPFX_REXB4) ? 0x08 : 0;
*(*buf)++ = b1;
unsigned b2 = opc >> 20 & 3;
b2 |= !(epfx & EPFX_REXX4) ? 0x04 : 0;
b2 |= (~(epfx >> EPFX_VVVV_IDX) & 0xf) << 3;
b2 |= opc & OPC_REXW ? 0x80 : 0;
*(*buf)++ = b2;
unsigned b3 = opc >> 33 & 7;
b3 |= (~(epfx >> EPFX_VVVV_IDX) & 0x10) >> 1;
b3 |= opc & OPC_EVEXB ? 0x10 : 0;
b3 |= (opc >> 23 & 3) << 5;
b3 |= opc & OPC_EVEXZ ? 0x80 : 0;
*(*buf)++ = b3;
} else if (opc & OPC_VEXL0) {
if (epfx & (EPFX_REXR4 | EPFX_REXX4 | EPFX_REXB4 | (0x10 << EPFX_VVVV_IDX)))
return -1;
bool vex3 = opc & (OPC_REXW | 0x20000) || epfx & (EPFX_REXX | EPFX_REXB);
unsigned pp = opc >> 20 & 3;
*(*buf)++ = 0xc4 | !vex3;
unsigned b2 = pp | (opc & 0x800000 ? 0x4 : 0);
if (vex3) {
unsigned b1 = opc >> 16 & 7;
b1 |= !(epfx & EPFX_REXR) ? 0x80 : 0;
b1 |= !(epfx & EPFX_REXX) ? 0x40 : 0;
b1 |= !(epfx & EPFX_REXB) ? 0x20 : 0;
*(*buf)++ = b1;
b2 |= opc & OPC_REXW ? 0x80 : 0;
} else {
b2 |= !(epfx & EPFX_REXR) ? 0x80 : 0;
}
b2 |= (~(epfx >> EPFX_VVVV_IDX) & 0xf) << 3;
*(*buf)++ = b2;
} else {
if (opc & OPC_LOCK)
*(*buf)++ = 0xF0;
if (opc & OPC_66)
*(*buf)++ = 0x66;
if (opc & OPC_F2)
*(*buf)++ = 0xF2;
if (opc & OPC_F3)
*(*buf)++ = 0xF3;
if (opc & OPC_REXW || epfx & (EPFX_REX_MSK)) {
unsigned rex = 0x40;
rex |= opc & OPC_REXW ? 8 : 0;
rex |= epfx & EPFX_REXR ? 4 : 0;
rex |= epfx & EPFX_REXX ? 2 : 0;
rex |= epfx & EPFX_REXB ? 1 : 0;
*(*buf)++ = rex;
}
if (opc & 0x30000)
*(*buf)++ = 0x0F;
if ((opc & 0x30000) == 0x20000)
*(*buf)++ = 0x38;
if ((opc & 0x30000) == 0x30000)
*(*buf)++ = 0x3A;
}
*(*buf)++ = opc & 0xff;
if (opc & 0x8000)
*(*buf)++ = (opc >> 8) & 0xff;
return 0;
}
static int enc_imm(uint8_t** restrict buf, uint64_t imm, unsigned immsz) {
if (!op_imm_n(imm, immsz))
return -1;
for (unsigned i = 0; i < immsz; i++)
*(*buf)++ = imm >> 8 * i;
return 0;
}
static int enc_o(uint8_t** restrict buf, uint64_t opc, uint64_t epfx,
uint64_t op0) {
if (op_reg_idx(op0) & 0x8)
epfx |= EPFX_REXB;
// NB: this cannot happen. There is only one O-encoded instruction which
// accepts high-byte registers (b0+/MOVABS Rb,Ib), which will never have a
// REx prefix if the operand is a high-byte register.
// bool has_rex = opc & OPC_REXW || epfx & EPFX_REX_MSK;
// if (has_rex && op_reg_gph(op0)) return -1;
if (enc_opc(buf, opc, epfx))
return -1;
*(*buf - 1) = (*(*buf - 1) & 0xf8) | (op_reg_idx(op0) & 0x7);
return 0;
}
static int enc_mr(uint8_t** restrict buf, uint64_t opc, uint64_t epfx,
uint64_t op0, uint64_t op1, unsigned immsz) {
// If !op_reg(op1), it is a constant value for ModRM.reg
if (op_reg(op0) && (op_reg_idx(op0) & 0x8))
epfx |= EPFX_REXB;
if (op_reg(op0) && (op_reg_idx(op0) & 0x10))
epfx |= 0 ? EPFX_REXB4 : EPFX_REXX | EPFX_EVEX;
if (op_mem(op0) && (op_mem_base(op0) & 0x8))
epfx |= EPFX_REXB;
if (op_mem(op0) && (op_mem_base(op0) & 0x10))
epfx |= EPFX_REXB4;
if (op_mem(op0) && (op_mem_idx(op0) & 0x8))
epfx |= EPFX_REXX;
if (op_mem(op0) && (op_mem_idx(op0) & 0x10))
epfx |= opc & OPC_VSIB ? 0x10 << EPFX_VVVV_IDX : EPFX_REXX4;
if (op_reg(op1) && (op_reg_idx(op1) & 0x8))
epfx |= EPFX_REXR;
if (op_reg(op1) && (op_reg_idx(op1) & 0x10))
epfx |= EPFX_REXR4;
bool has_rex =
opc & (OPC_REXW | OPC_VEXL0 | OPC_EVEXL0) || (epfx & EPFX_REX_MSK);
if (has_rex && (op_reg_gph(op0) || op_reg_gph(op1)))
return -1;
if (epfx & (EPFX_EVEX | EPFX_REXB4 | EPFX_REXX4 | EPFX_REXR4 |
(0x10 << EPFX_VVVV_IDX))) {
if (!(opc & OPC_EVEXL0))
return -1;
} else if (opc & OPC_DOWNGRADE_VEX) { // downgrade EVEX to VEX
// clear EVEX and disp8scale, set VEX
opc = (opc & ~(uint64_t)(OPC_EVEXL0 | OPC_EVEX_DISP8SCALE)) | OPC_VEXL0;
if (opc & OPC_DOWNGRADE_VEX_FLIPW)
opc ^= OPC_REXW;
}
if (LIKELY(op_reg(op0))) {
if (enc_opc(buf, opc, epfx))
return -1;
*(*buf)++ = 0xc0 | ((op_reg_idx(op1) & 7) << 3) | (op_reg_idx(op0) & 7);
return 0;
}
unsigned opcsz = opc_size(opc, epfx);
int mod = 0, reg = op1 & 7, rm;
int scale = 0, idx = 4, base = 0;
int32_t off = op_mem_offset(op0);
bool withsib = opc & OPC_FORCE_SIB;
if (!!op_mem_idx(op0) != !!op_mem_scale(op0))
return -1;
if (!op_mem_idx(op0) && (opc & OPC_VSIB))
return -1;
if (op_mem_idx(op0)) {
if (opc & OPC_VSIB) {
if (!op_reg_xmm(op_mem_idx(op0)))
return -1;
// EVEX VSIB requires non-zero opmask
if ((opc & OPC_EVEXL0) && !(opc & OPC_MASK_MSK))
return -1;
} else {
if (!op_reg_gpl(op_mem_idx(op0)))
return -1;
if (op_reg_idx(op_mem_idx(op0)) == 4)
return -1;
}
idx = op_mem_idx(op0) & 7;
int scalabs = op_mem_scale(op0);
if (scalabs & (scalabs - 1))
return -1;
scale = (scalabs & 0xA ? 1 : 0) | (scalabs & 0xC ? 2 : 0);
withsib = true;
}
unsigned dispsz = 0;
if (!op_mem_base(op0)) {
base = 5;
rm = 4;
dispsz = 4;
} else if (op_mem_base(op0) == FE_IP) {
rm = 5;
dispsz = 4;
// Adjust offset, caller doesn't know instruction length.
off -= opcsz + 5 + immsz;
if (withsib)
return -1;
} else {
if (!op_reg_gpl(op_mem_base(op0)))
return -1;
rm = op_reg_idx(op_mem_base(op0)) & 7;
if (withsib || rm == 4) {
base = rm;
rm = 4;
}
if (off) {
unsigned disp8scale = (opc & OPC_EVEX_DISP8SCALE) >> 39;
if (!(off & ((1 << disp8scale) - 1)) && op_imm_n(off >> disp8scale, 1)) {
mod = 0x40;
dispsz = 1;
off >>= disp8scale;
} else {
mod = 0x80;
dispsz = 4;
}
} else if (rm == 5) {
mod = 0x40;
dispsz = 1;
}
}
if (opcsz + 1 + (rm == 4) + dispsz + immsz > 15)
return -1;
if (enc_opc(buf, opc, epfx))
return -1;
*(*buf)++ = mod | (reg << 3) | rm;
if (UNLIKELY(rm == 4))
*(*buf)++ = (scale << 6) | (idx << 3) | base;
return enc_imm(buf, off, dispsz);
}
typedef enum {
ENC_NP,
ENC_M,
ENC_R,
ENC_M1,
ENC_MC,
ENC_MR,
ENC_RM,
ENC_RMA,
ENC_MRC,
ENC_AM,
ENC_MA,
ENC_I,
ENC_O,
ENC_OA,
ENC_S,
ENC_A,
ENC_D,
ENC_FD,
ENC_TD,
ENC_IM,
ENC_RVM,
ENC_RVMR,
ENC_RMV,
ENC_VM,
ENC_MVR,
ENC_MRV,
ENC_MAX
} Encoding;
struct EncodingInfo {
uint8_t modrm : 2;
uint8_t modreg : 2;
uint8_t vexreg : 2;
uint8_t immidx : 2;
// 0 = normal or jump, 1 = constant 1, 2 = address-size, 3 = RVMR
uint8_t immctl : 3;
uint8_t zregidx : 2;
uint8_t zregval : 1;
};
const struct EncodingInfo encoding_infos[ENC_MAX] = {
// clang-format off
[ENC_NP] = {0},
[ENC_M] = {.modrm = 0x0^3, .immidx = 1},
[ENC_R] = {.modreg = 0x0^3},
[ENC_M1] = {.modrm = 0x0^3, .immctl = 1, .immidx = 1},
[ENC_MC] = {.modrm = 0x0^3, .zregidx = 0x1^3, .zregval = 1},
[ENC_MR] = {.modrm = 0x0^3, .modreg = 0x1^3, .immidx = 2},
[ENC_RM] = {.modrm = 0x1^3, .modreg = 0x0^3, .immidx = 2},
[ENC_RMA] = {.modrm = 0x1^3, .modreg = 0x0^3, .zregidx = 0x2^3, .zregval = 0},
[ENC_MRC] = {.modrm = 0x0^3, .modreg = 0x1^3, .zregidx = 0x2^3, .zregval = 1},
[ENC_AM] = {.modrm = 0x1^3, .zregidx = 0x0^3, .zregval = 0},
[ENC_MA] = {.modrm = 0x0^3, .zregidx = 0x1^3, .zregval = 0},
[ENC_I] = {.immidx = 0},
[ENC_O] = {.modreg = 0x0^3, .immidx = 1},
[ENC_OA] = {.modreg = 0x0^3, .zregidx = 0x1^3, .zregval = 0},
[ENC_S] = {0},
[ENC_A] = {.zregidx = 0x0^3, .zregval = 0, .immidx = 1},
[ENC_D] = {.immidx = 0},
[ENC_FD] = {.zregidx = 0x0^3, .zregval = 0, .immctl = 2, .immidx = 1},
[ENC_TD] = {.zregidx = 0x1^3, .zregval = 0, .immctl = 2, .immidx = 0},
[ENC_IM] = {.modrm = 0x1^3, .immidx = 0},
[ENC_RVM] = {.modrm = 0x2^3, .modreg = 0x0^3, .vexreg = 0x1^3, .immidx = 3},
[ENC_RVMR] = {.modrm = 0x2^3, .modreg = 0x0^3, .vexreg = 0x1^3, .immctl = 3, .immidx = 3},
[ENC_RMV] = {.modrm = 0x1^3, .modreg = 0x0^3, .vexreg = 0x2^3},
[ENC_VM] = {.modrm = 0x1^3, .vexreg = 0x0^3, .immidx = 2},
[ENC_MVR] = {.modrm = 0x0^3, .modreg = 0x2^3, .vexreg = 0x1^3},
[ENC_MRV] = {.modrm = 0x0^3, .modreg = 0x1^3, .vexreg = 0x2^3},
// clang-format on
};
static const uint64_t alt_tab[] = {
#include <fadec-encode-private.inc>
};
int fe_enc64_impl(uint8_t** restrict buf, uint64_t opc, FeOp op0, FeOp op1,
FeOp op2, FeOp op3) {
uint8_t* buf_start = *buf;
uint64_t ops[4] = {op0, op1, op2, op3};
uint64_t epfx = 0;
// Doesn't change between variants
if ((opc & OPC_GPH_OP0) && op_reg_gpl(op0) && op0 >= FE_SP)
epfx |= EPFX_REX;
else if (!(opc & OPC_GPH_OP0) && op_reg_gph(op0))
goto fail;
if ((opc & OPC_GPH_OP1) && op_reg_gpl(op1) && op1 >= FE_SP)
epfx |= EPFX_REX;
else if (!(opc & OPC_GPH_OP1) && op_reg_gph(op1))
goto fail;
try_encode:;
unsigned enc = (opc >> 51) & 0x1f;
const struct EncodingInfo* ei = &encoding_infos[enc];
int64_t imm = 0xcc;
unsigned immsz = (opc >> 47) & 0xf;
if (UNLIKELY(ei->zregidx && op_reg_idx(ops[ei->zregidx ^ 3]) != ei->zregval))
goto next;
if (UNLIKELY(enc == ENC_S)) {
if ((op_reg_idx(op0) << 3 & 0x20) != (opc & 0x20))
goto next;
opc |= op_reg_idx(op0) << 3;
}
if (immsz) {
imm = ops[ei->immidx];
if (UNLIKELY(ei->immctl)) {
if (ei->immctl == 2) {
immsz = UNLIKELY(opc & OPC_67) ? 4 : 8;
if (immsz == 4)
imm = (int32_t)imm; // address are zero-extended
} else if (ei->immctl == 3) {
if (!op_reg_xmm(imm))
goto fail;
imm = op_reg_idx(imm) << 4;
if (!op_imm_n(imm, 1))
goto fail;
} else if (ei->immctl == 1) {
if (imm != 1)
goto next;
immsz = 0;
}
} else if (enc == ENC_D) {
imm -= (int64_t)*buf + opc_size(opc, epfx) + immsz;
bool has_alt = opc >> 56 != 0;
bool skip_to_alt = has_alt && UNLIKELY(opc & FE_JMPL);
if (skip_to_alt || !op_imm_n(imm, immsz)) {
if (!has_alt)
goto fail;
// JMP/Jcc special case
immsz = 4;
if (opc & 0x80) { // JMP
opc -= 2; // Convert opcode 0xeb to 0xe9
imm -= 3; // 3 extra immediate bytes
} else { // Jcc
opc += 0x10010; // Add 0f escape + 0x10 to opcode
imm -= 4; // 0f escape + 3 extra immediate bytes
}
if (!op_imm_n(imm, immsz))
goto fail;
}
} else {
if (!op_imm_n(imm, immsz))
goto next;
}
}
// NOP has no operands, so this must be the 32-bit OA XCHG
if ((opc & 0xfffffff) == 0x90 && ops[0] == FE_AX)
goto next;
if (UNLIKELY(enc == ENC_R)) {
if (enc_mr(buf, opc, epfx, 0, ops[0], immsz))
goto fail;
} else if (ei->modrm) {
FeOp modreg = ei->modreg ? ops[ei->modreg ^ 3] : (opc & 0xff00) >> 8;
if (ei->vexreg)
epfx |= ((uint64_t)op_reg_idx(ops[ei->vexreg ^ 3])) << EPFX_VVVV_IDX;
// Can fail for upgrade to EVEX due to high register numbers
if (enc_mr(buf, opc, epfx, ops[ei->modrm ^ 3], modreg, immsz))
goto next;
} else if (ei->modreg) {
if (enc_o(buf, opc, epfx, ops[ei->modreg ^ 3]))
goto fail;
} else {
if (enc_opc(buf, opc, epfx))
goto fail;
}
if (immsz)
if (enc_imm(buf, imm, immsz))
goto fail;
return 0;
next:;
uint64_t alt = opc >> 56;
if (alt) { // try alternative encoding, if available
opc = alt_tab[alt] | (opc & OPC_USER_MSK);
goto try_encode;
}
fail:
// Don't advance buffer on error; though we shouldn't write anything.
*buf = buf_start;
return -1;
}