pass-lang/src/x86encode.c

436 lines
10 KiB
C

/// This file handles the (context-free) encoding of x86 instructions.
/// It may substitute an instruction for a shorter one, but is unable to fuse instructions.
// REFERENCES:
// http://ref.x86asm.net/index.html (geek64-abc)
// https://wiki.osdev.org/X86-64_Instruction_Encoding
// https://defuse.ca/online-x86-assembler.htm
#include "format.h"
#include "x86encode.h"
#define REX 0x40
// REX prefix with 64-bit operands set
#define REX_W 0x48
#define REX_R 0x44
#define REX_X 0x42
#define REX_B 0x41
// lower 3 bits of register (not including part encoded in REX)
#define REG(r) (r & 7)
#define MODRM_RR (3 << 6)
#define MODRM_RM (0 << 6)
#define MODRM_RM8 (1 << 6)
#define MODRM_RM32 (2 << 6)
static void x86_opt_rexr(reg reg) {
if (reg >= R8) {
append_u8(REX | REX_R);
}
}
static void x86_opt_rexb(reg reg) {
if (reg >= R8) {
append_u8(REX | REX_B);
}
}
static void x86_opt_rexrb(reg r, reg b) {
uint8_t rex = REX;
if (r >= R8) {
rex |= REX_R;
}
if (b >= R8) {
rex |= REX_B;
}
if (rex != REX) {
append_u8(rex);
}
}
static void x86_rexwr(reg reg) {
uint8_t rex = REX | REX_W;
if (reg >= R8) rex |= REX_R;
append_u8(rex);
}
static void x86_rexwb(reg b) {
uint8_t rex = REX | REX_W;
if (b >= R8) rex |= REX_B;
append_u8(rex);
}
static void x86_rexwrb(reg r, reg b) {
uint8_t rex = REX | REX_W;
if (r >= R8) rex |= REX_R;
if (b >= R8) rex |= REX_B;
append_u8(rex);
}
static void x86_modrr(reg r, reg b) {
append_u8(MODRM_RR | (REG(r) << 3) | REG(b));
}
static void x86_modrm(reg r, reg b) {
append_u8(MODRM_RM | (REG(r) << 3) | REG(b));
}
static void x86_modrm8(reg r, reg b) {
append_u8(MODRM_RM8 | (REG(r) << 3) | REG(b));
}
static void x86_modrm32(reg r, reg b) {
append_u8(MODRM_RM32 | (REG(r) << 3) | REG(b));
}
static void x86_modxm(uint8_t ext, reg b) {
append_u8(MODRM_RR | (ext << 3) | REG(b));
}
static void x86_enc_opr(uint8_t op, reg reg) {
x86_opt_rexb(reg);
append_u8(op + REG(reg));
}
static void x86_enc_rexw_opr(uint8_t op, reg reg) {
x86_rexwb(reg);
append_u8(op + REG(reg));
}
static void x86_enc_opr_imm32(uint8_t op, reg reg, uint32_t imm) {
x86_opt_rexb(reg);
append_u8(op + REG(reg));
append_u32(imm);
}
static void x86_enc_rexw_opr_imm32(uint8_t op, reg reg, uint32_t imm) {
x86_rexwb(reg);
append_u8(op + REG(reg));
append_u32(imm);
}
static void x86_enc_rexw_opr_imm64(uint8_t op, reg reg, uint64_t imm) {
x86_rexwb(reg);
append_u8(op + REG(reg));
append_u64(imm);
}
static void x86_enc_rexw_modrr(uint8_t op, reg r, reg m) {
x86_rexwrb(r, m);
append_u8(op);
x86_modrr(r, m);
}
static void x86_enc_rexw_modrm(uint8_t op, reg r, reg b) {
x86_rexwrb(r, b);
append_u8(op);
x86_modrm(r, b);
}
static void x86_enc_rexw_modrm8(uint8_t op, reg r, reg b, int8_t disp) {
x86_rexwrb(r, b);
append_u8(op);
x86_modrm8(r, b);
append_u8(disp);
}
static void x86_enc_rexw_modrm32(uint8_t op, reg r, reg b, int32_t disp) {
x86_rexwrb(r, b);
append_u8(op);
x86_modrm32(r, b);
append_u32(disp);
}
static void x86_enc_rexw_modrmd(uint8_t op, reg r, reg b, int32_t disp) {
if (disp == 0 && b != BP && b != R13) {
x86_enc_rexw_modrm(op, r, b);
} else if (disp >= INT8_MIN && disp <= INT8_MAX) {
x86_enc_rexw_modrm8(op, r, b, (int8_t) disp);
} else {
x86_enc_rexw_modrm32(op, r, b, disp);
}
}
static void x86_enc_rexw_modxm_imm8(uint8_t op, uint8_t ext, reg m, uint8_t imm) {
x86_rexwb(m);
append_u8(op);
x86_modxm(ext, m);
append_u8(imm);
}
static void x86_enc_rexw_modxm_imm32(uint8_t op, uint8_t ext, reg m, uint32_t imm) {
x86_rexwb(m);
append_u8(op);
x86_modxm(ext, m);
append_u32(imm);
}
static void x86_enc_rexw_modxm_imm(uint8_t op, uint8_t ext, reg m, uint32_t imm) {
if (imm <= UINT8_MAX) {
x86_enc_rexw_modxm_imm8(op, ext, m, (uint8_t) imm);
} else {
x86_enc_rexw_modxm_imm32(op, ext, m, imm);
}
}
static void x86_enc_modrr(uint8_t op, reg r, reg b) {
x86_opt_rexrb(r, b);
append_u8(op);
x86_modrr(r, b);
}
static void x86_enc_disp8(uint8_t op, int8_t disp) {
uint8_t buf[2] = { op, (uint8_t) disp };
append_data(2, buf);
}
static void x86_enc_disp32(uint8_t op, int32_t disp) {
append_u8(op);
append_u32((uint32_t) disp);
}
void x86_inst_mov_r64_imm64(reg dest, uint64_t imm) {
x86_enc_rexw_opr_imm64(0xb8, dest, imm);
}
void x86_inst_mov_r64_imm32(reg dest, uint32_t imm) {
x86_enc_opr_imm32(0xb8, dest, imm);
}
void x86_inst_mov_r64_imm(reg dest, uint64_t imm) {
// TODO: xor if 0, use inc and dec, 16-bit and 8-bit immediates, sign extension
if (imm <= UINT32_MAX) {
x86_inst_mov_r64_imm32(dest, (uint32_t) imm);
} else {
x86_inst_mov_r64_imm64(dest, imm);
}
}
void x86_inst_mov_r64_r64(reg dest, reg src) {
x86_enc_rexw_modrr(0x8b, dest, src);
}
void x86_inst_mov_r64_m64(reg dest, reg src) {
x86_enc_rexw_modrm(0x8b, dest, src);
}
void x86_inst_mov_r64_m64_disp8(reg dest, reg src, int8_t disp) {
x86_enc_rexw_modrm8(0x8b, dest, src, disp);
}
void x86_inst_mov_r64_m64_disp32(reg dest, reg src, int32_t disp) {
x86_enc_rexw_modrm32(0x8b, dest, src, disp);
}
void x86_inst_mov_r64_m64_disp(reg dest, reg src, int32_t disp) {
x86_enc_rexw_modrmd(0x8b, dest, src, disp);
}
void x86_inst_mov_m64_r64(reg dest, reg src) {
x86_enc_rexw_modrm(0x89, src, dest);
}
void x86_inst_mov_m64_r64_disp8(reg dest, reg src, int8_t disp) {
x86_enc_rexw_modrm8(0x89, src, dest, disp);
}
void x86_inst_mov_m64_r64_disp32(reg dest, reg src, int32_t disp) {
x86_enc_rexw_modrm32(0x89, src, dest, disp);
}
void x86_inst_mov_m64_r64_disp(reg dest, reg src, int32_t disp) {
x86_enc_rexw_modrmd(0x89, src, dest, disp);
}
void x86_inst_mov_r32_r32(reg dest, reg src) {
x86_enc_modrr(0x8b, dest, src);
}
void x86_inst_xchg_r64_rax(reg src) {
x86_enc_rexw_opr(0x90, src);
}
void x86_inst_xchg_r64_r64(reg dest, reg src) {
if (src == AX) {
x86_inst_xchg_r64_rax(dest);
} else if (dest == AX) {
x86_inst_xchg_r64_rax(src);
} else {
x86_enc_rexw_modrr(0x87, dest, src);
}
}
void x86_inst_xchg_r64_m64(reg dest, reg src) {
x86_enc_rexw_modrm(0x87, dest, src);
}
void x86_inst_xchg_r64_m64_disp8(reg dest, reg src, int8_t disp) {
x86_enc_rexw_modrm8(0x87, dest, src, disp);
}
void x86_inst_xchg_r64_m64_disp32(reg dest, reg src, int32_t disp) {
x86_enc_rexw_modrm32(0x87, dest, src, disp);
}
void x86_inst_xchg_r64_m64_disp(reg dest, reg src, int32_t disp) {
if (disp == 0) {
x86_inst_xchg_r64_r64(dest, src);
} else {
x86_enc_rexw_modrmd(0x87, dest, src, disp);
}
}
void x86_inst_push_r64(reg reg) {
x86_enc_opr(0x50, reg);
}
void x86_inst_pop_r64(reg reg) {
x86_enc_opr(0x58, reg);
}
void x86_inst_test_r64_r64(reg r1, reg r2) {
x86_enc_rexw_modrr(0x85, r1, r2);
}
void x86_inst_test_r8_r8(reg r1, reg r2) {
x86_enc_modrr(0x84, r1, r2);
}
void x86_inst_test_m8_imm8(reg r, uint8_t imm) {
x86_opt_rexr(r);
x86_enc_opr(0xf6, r);
append_u8(imm);
}
void x86_inst_jmp_disp8(int8_t disp) {
x86_enc_disp8(0xeb, disp);
}
void x86_inst_jmp_disp32(int32_t disp) {
x86_enc_disp32(0xe9, disp);
}
void x86_inst_jmp_disp(int32_t disp) {
int32_t disp8 = disp + X86_JMP_DISP8_SIZE;
if (disp8 >= INT8_MIN && disp8 <= INT8_MAX) {
x86_inst_jmp_disp8((int8_t) disp8);
} else {
x86_inst_jmp_disp32(disp + X86_JMP_DISP32_SIZE);
}
}
void x86_inst_jmp_disp32_op(void) {
append_u8(0xe9);
}
void x86_inst_jnz_disp8(int8_t disp) {
x86_enc_disp8(0x75, disp);
}
void x86_inst_jnz_disp32(int32_t disp) {
append_u8(0x0f);
x86_enc_disp32(0x85, disp);
}
void x86_inst_jnz_disp(int32_t disp) {
int32_t disp8 = disp + X86_JNZ_DISP8_SIZE;
if (disp8 >= INT8_MIN && disp8 <= INT8_MAX) {
x86_inst_jnz_disp8((int8_t) disp);
} else {
x86_inst_jnz_disp32(disp + X86_JNZ_DISP32_SIZE);
}
}
void x86_inst_jnz_disp32_op(void) {
append_u8(0x0f);
append_u8(0x84);
}
void x86_inst_jz_disp8(int8_t disp) {
x86_enc_disp8(0x74, disp);
}
void x86_inst_jz_disp32(int32_t disp) {
append_u8(0x0f);
x86_enc_disp32(0x84, disp);
}
void x86_inst_jz_disp(int32_t disp) {
int32_t disp8 = disp + X86_JZ_DISP8_SIZE;
if (disp8 >= INT8_MIN && disp8 <= INT8_MAX) {
x86_inst_jz_disp8((int8_t) disp);
} else {
x86_inst_jz_disp32(disp + X86_JZ_DISP32_SIZE);
}
}
void x86_inst_jz_disp32_op(void) {
append_u8(0x0f);
append_u8(0x84);
}
void x86_inst_xor_r32_r32(reg dest, reg src) {
x86_enc_modrr(0x31, dest, src);
}
void x86_inst_xor_m8_imm8(reg dest, uint8_t imm) {
x86_opt_rexr(dest);
x86_enc_opr(0x80, dest);
append_u8(imm);
}
void x86_inst_xor_al_imm8(uint8_t imm) {
append_u8(0x34);
append_u8(imm);
}
void x86_zero(reg dest) {
x86_inst_xor_r32_r32(dest, dest);
}
// TODO: special instructions for AX
void x86_inst_sub_r64_imm8(reg dest, int8_t imm) {
x86_enc_rexw_modxm_imm8(0x83, 5, dest, (uint8_t) imm);
}
void x86_inst_sub_r64_imm32(reg dest, int32_t imm) {
x86_enc_rexw_modxm_imm32(0x81, 5, dest, (uint32_t) imm);
}
void x86_inst_sub_r64_imm(reg dest, int32_t imm) {
x86_enc_rexw_modxm_imm(0x81, 5, dest, (uint32_t) imm);
}
void x86_inst_sub_r64_r64(reg dest, reg src) {
x86_enc_rexw_modrr(0x2B, dest, src);
}
// TODO: in case of -128, choice of add vs. sub matters!
void x86_inst_add_r64_imm8(reg dest, int8_t imm) {
x86_enc_rexw_modxm_imm8(0x83, 0, dest, (uint8_t) imm);
}
void x86_inst_add_r64_r64(reg dest, reg src) {
x86_enc_rexw_modrr(0x01, src, dest);
}
void x86_inst_lea_r64_m64_disp8(reg dest, reg src, int8_t disp) {
x86_enc_rexw_modrm8(0x8d, dest, src, disp);
}
void x86_inst_lea_r64_rip_disp32_op(reg dest) {
x86_rexwr(dest);
append_u8(0x8d);
x86_modrm(dest, BP);
}
void x86_inst_lea_r64_rip_disp32(reg dest, int32_t disp) {
x86_enc_rexw_modrm32(0x8d, dest, BP, disp);
}
void x86_inst_syscall(void) {
const uint8_t buf[2] = { 0x0f, 0x05 };
append_data(2, buf);
}