pass-lang/src/asm.c

227 lines
5.6 KiB
C

// 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 "asm.h"
#include "io.h"
#include <stdlib.h>
#include <string.h>
ip here;
#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
#define REXQ_R(r) (REX | ((r >= R8) ? REX_R : 0))
#define REXQ_B(r) (REX | ((r >= R8) ? REX_B : 0))
// REX prefix including upper bit of register
#define REXQ_WR(r) (REX_W | REXQ_R(r))
#define REXQ_WRB(r, b) (REX_W | REXQ_R(r) | REXQ_B(b))
// 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_RD8 (1 << 6)
#define MODRM_RD32 (2 << 6)
#define MODRMQ(reg, rm) ((REG(reg) << 3) | REG(rm))
#define MODRMQ_RR(reg, rm) (MODRM_RR | MODRMQ(reg, rm))
#define MODRMQ_RM(reg, base) (MODRM_RM | MODRMQ(reg, base))
#define MODRMQ_RD8(reg, base) (MODRM_RD8 | MODRMQ(reg, base))
#define MODRMQ_RD32(reg, base) (MODRM_RD32 | MODRMQ(reg, base))
static int32_t rel_offs(ip from, ip to) {
// jumping through hoops to hopefully avoid UB
int64_t off = (int64_t) to - (int64_t) from;
if (off > INT32_MAX || off < INT32_MIN) {
fprintf(stderr, "displacement greater than 32 bits!\n");
fprintf(stderr, "if you ever encounter this error, let me know and I'll implement it.\n");
exit(1);
}
return (int32_t) off;
}
static int32_t rip_rel(ip there) {
return rel_offs(here, there);
}
#define JUMP_DISP8_SIZE 2
static void inst_jump_disp8(uint8_t disp) {
uint8_t inst[JUMP_DISP8_SIZE] = {
0xeb, // jmp Jbs
disp
};
emit(&inst, JUMP_DISP8_SIZE);
}
#define JUMP_DISP32_SIZE 5
static void inst_jump_disp32(uint32_t disp) {
emit_u8(0xe9); // jmp Jvds
emit_u32(disp);
}
void inst_jump(ip there) {
int32_t disp8 = rel_offs(here + JUMP_DISP8_SIZE, there);
if (disp8 < INT8_MAX && disp8 > INT8_MIN) {
inst_jump_disp8(disp8);
} else {
inst_jump_disp32(rel_offs(here + JUMP_DISP32_SIZE, there));
}
}
ip inst_jump_unresolved(void) {
inst_jump_disp32(0);
return here;
}
void inst_jump_resolve(ip disp, ip there) {
patch_i32(disp - 4, rel_offs(disp, there));
}
static void inst_mov_imm32(reg reg, uint32_t imm) {
// mov Zvqp Ivqp
if (reg >= R8) {
emit_u8(REXQ_R(reg));
}
emit_u8(0xb8 + REG(reg));
emit_u32(imm);
}
static void inst_mov_imm64(reg reg, uint64_t imm) {
// mov Zvqp Ivqp
uint8_t buf[10] = { REXQ_WR(reg), 0xb8 + REG(reg), 0, 0, 0, 0, 0, 0, 0, 0 };
memcpy(&buf[2], &imm, sizeof(uint64_t));
emit(buf, 10);
}
void inst_mov_imm(reg reg, uint64_t imm) {
// TODO: emit `mov ax`, `mov al`, xor, xor+inc, xor+neg
if (imm <= UINT32_MAX) {
inst_mov_imm32(reg, (uint32_t) imm);
} else {
inst_mov_imm64(reg, imm);
}
}
void inst_mov_imm_i64(reg reg, int64_t imm) {
// TODO: emit sign extensions
if (imm >= 0 && imm <= UINT32_MAX) {
inst_mov_imm32(reg, (uint32_t) imm);
} else {
inst_mov_imm64(reg, (uint64_t) imm);
}
}
static void check_base(reg base) {
if (base == SP || base == BP || base == R12 || base == R13) {
fprintf(stderr, "indirect addressing not implemented for sp & co\n");
exit(1);
}
}
void inst_mov(reg dest, reg src) {
// mov Evqp Gvqp
emit_u8(REXQ_WRB(dest, src));
emit_u8(0x89);
emit_u8(MODRMQ_RR(src, dest));
}
void inst_mov_from(reg dest, reg base) {
check_base(base);
// mov Gvqp Evqp
emit_u8(REXQ_WRB(base, dest));
emit_u8(0x8B);
emit_u8(MODRMQ_RM(dest, base));
}
static void inst_mov_from_disp8(reg dest, reg base, int8_t disp) {
check_base(base);
// mov Gvqp Evqp
emit_u8(REXQ_WRB(base, dest));
emit_u8(0x8B);
emit_u8(MODRMQ_RD8(dest, base));
emit_u8(disp);
}
static void inst_mov_from_disp32(reg dest, reg base, int32_t disp) {
check_base(base);
// mov Gvqp Evqp
emit_u8(REXQ_WRB(base, dest));
emit_u8(0x8B);
emit_u8(MODRMQ_RD32(dest, base));
emit_u32((int32_t) disp);
}
void inst_mov_from_disp(reg dest, reg base, int32_t disp) {
if (disp == 0) {
inst_mov_from(dest, base);
} else if (disp <= INT8_MAX && disp >= INT8_MIN) {
inst_mov_from_disp8(dest, base, (int8_t) disp);
} else {
inst_mov_from_disp32(dest, base, disp);
}
}
void inst_mov_to(reg base, reg src) {
check_base(base);
// mov Evqp Gvqp
emit_u8(REXQ_WRB(base, src));
emit_u8(0x89);
emit_u8(MODRMQ_RR(base, src));
}
static void inst_mov_to_disp8(reg base, reg src, int8_t disp) {
check_base(base);
// mov Evqp Gvqp
emit_u8(REXQ_WRB(base, src));
emit_u8(0x89);
emit_u8(MODRMQ_RD8(base, src));
emit_u8(disp);
}
static void inst_mov_to_disp32(reg base, reg src, int32_t disp) {
check_base(base);
// mov Evqp Gvqp
emit_u8(REXQ_WRB(base, src));
emit_u8(0x89);
emit_u8(MODRMQ_RD32(base, src));
emit_u32((uint32_t) disp);
}
void inst_mov_to_disp(reg base, reg src, int32_t disp) {
if (disp == 0) {
inst_mov_to(base, src);
} else if (disp <= INT8_MAX && disp >= INT8_MIN) {
inst_mov_to_disp8(base, src, (int8_t) disp);
} else {
inst_mov_to_disp32(base, src, disp);
}
}
void inst_syscall(void) {
const uint8_t buf[2] = { 0x0f, 0x05 };
emit(&buf, 2);
}
void inst_push(reg reg) {
// push Zvq
if (reg >= R8) {
emit_u8(REX_B);
}
emit_u8(0x50 + REG(reg));
}
void inst_pop(reg reg) {
// pop Zvq
if (reg >= R8) {
emit_u8(REX_B);
}
emit_u8(0x58 + REG(reg));
}