Three w6a defects, one component. MOVB with an SP/BP/SI/DI register operand encoded AH/CH/DH/BH -- emit_rex suppressed the bare 0x40 that selects SPL/BPL/SIL/DIL (the comment claimed "we always emit REX"; encode probe: `MOVB SI,(AX)` -> `88 30` = %dh). emit_rex8 forces the byte for low-byte codes 4-7; no current w6c output emits those shapes, so all existing objects are unchanged. LEAQ and CALL unsupported operand shapes fell through with zero bytes and no errs++ (every MOV arm reports); both now err. Every hardcoded os.write diagnostic length in the wwstage was one byte short (truncating the newline/quote); all converted to the .len idiom so the length cannot drift again.
869 lines
27 KiB
C
869 lines
27 KiB
C
/*
|
|
* Encoding subset: the instructions cgen emits today. Operand shapes
|
|
* we accept:
|
|
* MOVQ $imm, reg — C7 /0 imm32 (REX.W) [imm fits in i32]
|
|
* MOVQ reg, reg — 89 /r (REX.W)
|
|
* MOVQ off(reg), reg — 8B /r (REX.W)
|
|
* MOVQ reg, off(reg) — 89 /r (REX.W)
|
|
* ADDQ/SUBQ/AND/OR/XOR — 01/29/21/09/31 /r (REX.W) [reg→reg]
|
|
* ADDQ $imm, reg — 81 /0 imm32 (REX.W)
|
|
* SUBQ $imm, reg — 81 /5 imm32 (REX.W) (likewise CMPQ)
|
|
* IMULQ reg, reg — 0F AF /r (REX.W)
|
|
* IDIVQ reg — F7 /7 (REX.W)
|
|
* DIVQ reg — F7 /6 (REX.W) (unsigned)
|
|
* CQO — REX.W 99 (sign-extend RAX→RDX:RAX)
|
|
* NEGQ/NOTQ reg — F7 /3, F7 /2 (REX.W)
|
|
* SHLQ/SHRQ CL, reg — D3 /4, D3 /5 (REX.W)
|
|
* CMPQ reg, reg — 39 /r (REX.W)
|
|
* CMPQ $imm, reg — 81 /7 imm32 (REX.W)
|
|
* PUSHQ reg — 50+rd (REX.B for high)
|
|
* POPQ reg — 58+rd (REX.B for high)
|
|
* LEAQ name(SB), reg — 48 8D /r RIP-relative; reloc PC32
|
|
* LEAQ off(reg), reg — 48 8D /r
|
|
* CALL name(SB) — E8 cd reloc PLT32
|
|
* CALL reg — FF /2 (REX.W not strictly needed)
|
|
* RET — C3
|
|
* JMP/Jcc label — E9 cd / 0F 8x cd rel32 to local label
|
|
* SYSCALL — 0F 05
|
|
*/
|
|
#include "a.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
void
|
|
a_emit_byte(Asm *a, u8 b)
|
|
{
|
|
if (a->textlen + 1 > a->textcap) {
|
|
u64 nc = a->textcap ? a->textcap * 2 : 4096;
|
|
a->text = realloc(a->text, nc);
|
|
a->textcap = nc;
|
|
}
|
|
a->text[a->textlen++] = b;
|
|
}
|
|
|
|
void
|
|
a_emit_u32(Asm *a, u32 v)
|
|
{
|
|
a_emit_byte(a, (u8)(v & 0xff));
|
|
a_emit_byte(a, (u8)((v >> 8) & 0xff));
|
|
a_emit_byte(a, (u8)((v >> 16) & 0xff));
|
|
a_emit_byte(a, (u8)((v >> 24) & 0xff));
|
|
}
|
|
|
|
void
|
|
a_emit_data_byte(Asm *a, u8 b)
|
|
{
|
|
if (a->datalen + 1 > a->datacap) {
|
|
u64 nc = a->datacap ? a->datacap * 2 : 256;
|
|
a->data = realloc(a->data, nc);
|
|
a->datacap = nc;
|
|
}
|
|
a->data[a->datalen++] = b;
|
|
}
|
|
|
|
void
|
|
a_addreloc(Asm *a, u64 off, int kind, Asym *s, i64 add)
|
|
{
|
|
Areloc *r = calloc(1, sizeof *r);
|
|
r->off = off;
|
|
r->section = 0; /* .text */
|
|
r->kind = kind;
|
|
r->sym = s;
|
|
r->addend = add;
|
|
r->next = a->relocs;
|
|
a->relocs = r;
|
|
}
|
|
|
|
/* Record a relocation that lives in the .data section. Used by
|
|
* DATAR to patch a 64-bit slot with a symbol's runtime VA. obj.c
|
|
* separates these into .rela.data when emitting the .o. */
|
|
void
|
|
a_addreloc_data(Asm *a, u64 off, int kind, Asym *s, i64 add)
|
|
{
|
|
Areloc *r = calloc(1, sizeof *r);
|
|
r->off = off;
|
|
r->section = 1; /* .data */
|
|
r->kind = kind;
|
|
r->sym = s;
|
|
r->addend = add;
|
|
r->next = a->relocs;
|
|
a->relocs = r;
|
|
}
|
|
|
|
/* low 3 bits of register encoding */
|
|
static int
|
|
rcode(int r)
|
|
{
|
|
switch (r) {
|
|
case D_AX: return 0; case D_CX: return 1;
|
|
case D_DX: return 2; case D_BX: return 3;
|
|
case D_SP: return 4; case D_BP: return 5;
|
|
case D_SI: return 6; case D_DI: return 7;
|
|
case D_R8: return 0; case D_R9: return 1;
|
|
case D_R10:return 2; case D_R11:return 3;
|
|
case D_R12:return 4; case D_R13:return 5;
|
|
case D_R14:return 6; case D_R15:return 7;
|
|
case D_X0: return 0; case D_X1: return 1;
|
|
case D_X2: return 2; case D_X3: return 3;
|
|
case D_X4: return 4; case D_X5: return 5;
|
|
case D_X6: return 6; case D_X7: return 7;
|
|
case D_X8: return 0; case D_X9: return 1;
|
|
case D_X10:return 2; case D_X11:return 3;
|
|
case D_X12:return 4; case D_X13:return 5;
|
|
case D_X14:return 6; case D_X15:return 7;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 1 if r needs the high bit (REX.R or REX.B) */
|
|
static int
|
|
rhi(int r)
|
|
{
|
|
if (r >= D_R8 && r <= D_R15) return 1;
|
|
if (r >= D_X8 && r <= D_X15) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
is_xmm(int r)
|
|
{
|
|
return r >= D_X0 && r <= D_X15;
|
|
}
|
|
|
|
static u8
|
|
modrm(int mod, int reg, int rm)
|
|
{
|
|
return (u8)(((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7));
|
|
}
|
|
|
|
static void
|
|
emit_rex(Asm *a, int regbit, int rmbit, int w)
|
|
{
|
|
u8 b = 0x40;
|
|
if (w) b |= 0x08;
|
|
if (regbit) b |= 0x04;
|
|
if (rmbit) b |= 0x01;
|
|
if (b != 0x40 || w) a_emit_byte(a, b);
|
|
}
|
|
|
|
/* 8-bit register operands: SPL/BPL/SIL/DIL exist only WITH a REX
|
|
* prefix — bare, codes 4-7 select AH/CH/DH/BH. Force the 0x40 byte
|
|
* when the named r8 is one of them. */
|
|
static void
|
|
emit_rex8(Asm *a, int regbit, int rmbit, int r8)
|
|
{
|
|
u8 b = 0x40;
|
|
if (regbit) b |= 0x04;
|
|
if (rmbit) b |= 0x01;
|
|
if (b != 0x40 || (!rhi(r8) && rcode(r8) >= 4))
|
|
a_emit_byte(a, b);
|
|
}
|
|
|
|
/* Special-cases SP (needs SIB) and BP (forces disp). */
|
|
static void
|
|
emit_modrm_mem(Asm *a, int reg_field, int base, i64 disp)
|
|
{
|
|
int rm = rcode(base);
|
|
int mod;
|
|
int needsib = (rm == 4); /* SP requires SIB */
|
|
int forced_disp = (rm == 5 && disp == 0); /* BP needs explicit disp8 */
|
|
|
|
if (disp == 0 && !forced_disp) mod = 0;
|
|
else if (disp >= -128 && disp <= 127) mod = 1;
|
|
else mod = 2;
|
|
|
|
a_emit_byte(a, modrm(mod, reg_field, rm));
|
|
if (needsib)
|
|
a_emit_byte(a, (u8)(0x24)); /* SIB: scale=0 idx=4(none) base=4 */
|
|
if (mod == 1)
|
|
a_emit_byte(a, (u8)(disp & 0xff));
|
|
else if (mod == 2)
|
|
a_emit_u32(a, (u32)disp);
|
|
}
|
|
|
|
/* Plan 9 op order: src, dst. Generic two-reg encoding for ops that
|
|
* use the standard "reg, r/m" form (89 /r, 01 /r, etc.) — opcode
|
|
* implies the REX.W and the direction; we emit "src register goes
|
|
* into reg field, dst register into rm field". */
|
|
static void
|
|
encode_rr(Asm *a, u8 opcode, int src, int dst)
|
|
{
|
|
emit_rex(a, rhi(src), rhi(dst), 1);
|
|
a_emit_byte(a, opcode);
|
|
a_emit_byte(a, modrm(3, rcode(src), rcode(dst)));
|
|
}
|
|
|
|
static void
|
|
encode_rm(Asm *a, u8 opcode, int src_reg, int base, i64 disp)
|
|
{
|
|
emit_rex(a, rhi(src_reg), rhi(base), 1);
|
|
a_emit_byte(a, opcode);
|
|
emit_modrm_mem(a, rcode(src_reg), base, disp);
|
|
}
|
|
|
|
static void
|
|
encode_mr(Asm *a, u8 opcode, int dst_reg, int base, i64 disp)
|
|
{
|
|
emit_rex(a, rhi(dst_reg), rhi(base), 1);
|
|
a_emit_byte(a, opcode);
|
|
emit_modrm_mem(a, rcode(dst_reg), base, disp);
|
|
}
|
|
|
|
/* OPCODE /n imm32 reg form. E.g. ADDQ $imm, reg */
|
|
static void
|
|
encode_ri_imm32(Asm *a, u8 opcode, int subop, int dst, i32 imm)
|
|
{
|
|
emit_rex(a, 0, rhi(dst), 1);
|
|
a_emit_byte(a, opcode);
|
|
a_emit_byte(a, modrm(3, subop, rcode(dst)));
|
|
a_emit_u32(a, (u32)imm);
|
|
}
|
|
|
|
/* unary-on-reg: F7 /n reg, etc. */
|
|
static void
|
|
encode_unary(Asm *a, u8 opcode, int subop, int dst)
|
|
{
|
|
emit_rex(a, 0, rhi(dst), 1);
|
|
a_emit_byte(a, opcode);
|
|
a_emit_byte(a, modrm(3, subop, rcode(dst)));
|
|
}
|
|
|
|
/* SSE2 helpers. Plan 9 syntax: source first, destination second.
|
|
* For ADDSD-style ops we put dst in the reg field, src in r/m. */
|
|
static void
|
|
sse_rr(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op)
|
|
{
|
|
if (prefix) a_emit_byte(a, prefix);
|
|
emit_rex(a, rhi(reg_op), rhi(rm_op), 0);
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, op2);
|
|
a_emit_byte(a, modrm(3, rcode(reg_op), rcode(rm_op)));
|
|
}
|
|
|
|
static void
|
|
sse_mr_load(Asm *a, u8 prefix, u8 op2, int reg_op, int base, i64 disp)
|
|
{
|
|
if (prefix) a_emit_byte(a, prefix);
|
|
emit_rex(a, rhi(reg_op), rhi(base), 0);
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, op2);
|
|
emit_modrm_mem(a, rcode(reg_op), base, disp);
|
|
}
|
|
|
|
/* like sse_rr but encoded with REX.W (used by CVTTSD2SI / CVTSI2SD
|
|
* which target/source 64-bit integer regs) */
|
|
static void
|
|
sse_rr_w(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op)
|
|
{
|
|
if (prefix) a_emit_byte(a, prefix);
|
|
emit_rex(a, rhi(reg_op), rhi(rm_op), 1);
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, op2);
|
|
a_emit_byte(a, modrm(3, rcode(reg_op), rcode(rm_op)));
|
|
}
|
|
|
|
static u64
|
|
resolve_label(Asm *a, const char *name)
|
|
{
|
|
for (Asym *s = a->syms; s; s = s->next)
|
|
if (s->defined && strcmp(s->name, name) == 0)
|
|
return s->addr;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
label_defined(Asm *a, const char *name)
|
|
{
|
|
for (Asym *s = a->syms; s; s = s->next)
|
|
if (s->defined && strcmp(s->name, name) == 0) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* For local labels, we record a "fixup" — an offset in .text that
|
|
* needs to be patched once the label is resolved at end of pass. */
|
|
typedef struct Fixup Fixup;
|
|
struct Fixup {
|
|
u64 off; /* where the rel32 lands */
|
|
const char *label;
|
|
Fixup *next;
|
|
};
|
|
static Fixup *fixups;
|
|
|
|
static void
|
|
add_fixup(u64 off, const char *label)
|
|
{
|
|
Fixup *f = calloc(1, sizeof *f);
|
|
f->off = off;
|
|
f->label = strdup(label);
|
|
f->next = fixups;
|
|
fixups = f;
|
|
}
|
|
|
|
int
|
|
a_encode(Asm *a)
|
|
{
|
|
fixups = NULL;
|
|
const char *cur_text = NULL;
|
|
(void)cur_text;
|
|
for (Aprog *p = a->head; p; p = p->link) {
|
|
if (p->label) {
|
|
Asym *s = a_intern(a, p->label);
|
|
s->defined = 1;
|
|
s->is_text = 1;
|
|
s->addr = a->textlen;
|
|
}
|
|
switch (p->as) {
|
|
case A_NOP:
|
|
break;
|
|
case A_TEXT: {
|
|
Asym *s = a_intern(a, p->to.sym);
|
|
s->defined = 1;
|
|
s->is_text = 1;
|
|
s->is_global = 1;
|
|
s->addr = a->textlen;
|
|
cur_text = p->to.sym;
|
|
break;
|
|
}
|
|
case A_DATA: {
|
|
Asym *s = a_intern(a, p->to.sym);
|
|
s->defined = 1;
|
|
s->is_text = 1; /* we lay it out at the end of .text */
|
|
s->is_global = 1;
|
|
s->addr = a->textlen;
|
|
for (u64 i = 0; i < p->nbytes; i++)
|
|
a_emit_byte(a, p->bytes[i]);
|
|
break;
|
|
}
|
|
case A_DATAW: {
|
|
/* Writable variant: bytes go into .data (RW) instead
|
|
* of .text. obj.c emits the extra section conditionally
|
|
* on datalen > 0 so .o output stays byte-identical for
|
|
* inputs that don't use DATAW. */
|
|
Asym *s = a_intern(a, p->to.sym);
|
|
s->defined = 1;
|
|
s->is_data = 1;
|
|
s->is_global = 1;
|
|
s->addr = a->datalen;
|
|
for (u64 i = 0; i < p->nbytes; i++)
|
|
a_emit_data_byte(a, p->bytes[i]);
|
|
break;
|
|
}
|
|
case A_DATAR: {
|
|
/* DATAR slot+off(SB), target(SB) — record an
|
|
* R_X86_64_64 relocation at slot+off in .data
|
|
* pointing at target. The slot must already be
|
|
* defined by a prior DATAW (which emitted zero
|
|
* placeholder bytes the linker will overwrite). */
|
|
Asym *holder = a_intern(a, p->from.sym);
|
|
if (!holder->defined || !holder->is_data) {
|
|
fprintf(stderr,
|
|
"w6a: line %d: DATAR slot %s not yet defined as DATAW\n",
|
|
p->line, p->from.sym);
|
|
a->errs++;
|
|
break;
|
|
}
|
|
Asym *target = a_intern(a, p->to.sym);
|
|
u64 reloff = holder->addr + (u64)p->from.offset;
|
|
a_addreloc_data(a, reloff, 1 /* R_X86_64_64 */,
|
|
target, 0);
|
|
break;
|
|
}
|
|
case A_RET:
|
|
a_emit_byte(a, 0xC3);
|
|
break;
|
|
case A_SYSCALL:
|
|
a_emit_byte(a, 0x0F); a_emit_byte(a, 0x05);
|
|
break;
|
|
case A_PUSHQ:
|
|
if (rhi(p->to.type)) a_emit_byte(a, 0x41);
|
|
a_emit_byte(a, (u8)(0x50 + rcode(p->to.type)));
|
|
break;
|
|
case A_POPQ:
|
|
if (rhi(p->to.type)) a_emit_byte(a, 0x41);
|
|
a_emit_byte(a, (u8)(0x58 + rcode(p->to.type)));
|
|
break;
|
|
case A_NEGQ:
|
|
encode_unary(a, 0xF7, 3, p->to.type); break;
|
|
case A_NOTQ:
|
|
encode_unary(a, 0xF7, 2, p->to.type); break;
|
|
case A_IDIVQ:
|
|
encode_unary(a, 0xF7, 7, p->to.type); break;
|
|
case A_DIVQ:
|
|
/* unsigned divide; shares the F7 group with IDIVQ but
|
|
* uses /6 instead of /7. */
|
|
encode_unary(a, 0xF7, 6, p->to.type); break;
|
|
case A_CQO:
|
|
/* REX.W 99 — sign-extend RAX into RDX:RAX. */
|
|
a_emit_byte(a, 0x48); a_emit_byte(a, 0x99);
|
|
break;
|
|
|
|
case A_MOVQ:
|
|
if (p->from.type == D_CONST && p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
i64 v = p->from.offset;
|
|
if (v >= -2147483648LL && v <= 2147483647LL) {
|
|
/* C7 /0 imm32, sign-extended */
|
|
encode_ri_imm32(a, 0xC7, 0, p->to.type, (i32)v);
|
|
} else {
|
|
/* movabs r64, imm64: REX.W B8+rd imm64 */
|
|
emit_rex(a, 0, rhi(p->to.type), 1);
|
|
a_emit_byte(a, (u8)(0xB8 + rcode(p->to.type)));
|
|
for (int k = 0; k < 8; k++)
|
|
a_emit_byte(a, (u8)((v >> (k * 8)) & 0xff));
|
|
}
|
|
} else if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
encode_rr(a, 0x89, p->from.type, p->to.type);
|
|
} else if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
encode_mr(a, 0x8B, p->to.type, p->from.reg, p->from.offset);
|
|
} else if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type == D_INDIR) {
|
|
encode_rm(a, 0x89, p->from.type, p->to.reg, p->to.offset);
|
|
} else if (p->from.type == D_CONST
|
|
&& p->to.type == D_INDIR) {
|
|
/* MOVQ $imm32, r/m64 — C7 /0 (REX.W) imm32.
|
|
* The CPU sign-extends imm32 into 64 bits, so
|
|
* any value within i32 range works. */
|
|
emit_rex(a, 0, rhi(p->to.reg), 1);
|
|
a_emit_byte(a, 0xC7);
|
|
emit_modrm_mem(a, 0, p->to.reg, p->to.offset);
|
|
a_emit_u32(a, (u32)(i32)p->from.offset);
|
|
} else if (p->from.type == D_EXTERN
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
/* RIP-relative load: 48 8B /r mod=00 rm=5 disp32 */
|
|
emit_rex(a, rhi(p->to.type), 0, 1);
|
|
a_emit_byte(a, 0x8B);
|
|
a_emit_byte(a, modrm(0, rcode(p->to.type), 5));
|
|
u64 reloff = a->textlen;
|
|
a_emit_u32(a, 0);
|
|
Asym *s = a_intern(a, p->from.sym);
|
|
a_addreloc(a, reloff, 2, s, -4);
|
|
} else if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type == D_EXTERN) {
|
|
/* RIP-relative store: 48 89 /r mod=00 rm=5 disp32 */
|
|
emit_rex(a, rhi(p->from.type), 0, 1);
|
|
a_emit_byte(a, 0x89);
|
|
a_emit_byte(a, modrm(0, rcode(p->from.type), 5));
|
|
u64 reloff = a->textlen;
|
|
a_emit_u32(a, 0);
|
|
Asym *s = a_intern(a, p->to.sym);
|
|
a_addreloc(a, reloff, 2, s, -4);
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVQ shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_MOVW:
|
|
/* 16-bit MOV: prefix 0x66 selects 16-bit operand size.
|
|
* MOV r/m16, r16 — 66 89 /r; MOV r16, r/m16 — 66 8B /r.
|
|
* No REX.W (operand-size prefix beats REX.W). */
|
|
if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type == D_INDIR) {
|
|
a_emit_byte(a, 0x66);
|
|
emit_rex(a, rhi(p->from.type), rhi(p->to.reg), 0);
|
|
a_emit_byte(a, 0x89);
|
|
emit_modrm_mem(a, rcode(p->from.type),
|
|
p->to.reg, p->to.offset);
|
|
} else if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
a_emit_byte(a, 0x66);
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 0);
|
|
a_emit_byte(a, 0x8B);
|
|
emit_modrm_mem(a, rcode(p->to.type),
|
|
p->from.reg, p->from.offset);
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVW shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_MOVZWQ:
|
|
/* MOVZX r64, r/m16 — 0F B7 /r with REX.W */
|
|
if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 1);
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, 0xB7);
|
|
emit_modrm_mem(a, rcode(p->to.type),
|
|
p->from.reg, p->from.offset);
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVZWQ shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_MOVSWQ:
|
|
/* MOVSX r64, r/m16 — 0F BF /r with REX.W */
|
|
if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 1);
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, 0xBF);
|
|
emit_modrm_mem(a, rcode(p->to.type),
|
|
p->from.reg, p->from.offset);
|
|
} else if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.type), 1);
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, 0xBF);
|
|
a_emit_byte(a, modrm(3,
|
|
rcode(p->to.type), rcode(p->from.type)));
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVSWQ shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_MOVSBQ:
|
|
/* MOVSX r64, r/m8 — 0F BE /r with REX.W */
|
|
if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 1);
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, 0xBE);
|
|
emit_modrm_mem(a, rcode(p->to.type),
|
|
p->from.reg, p->from.offset);
|
|
} else if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.type), 1);
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, 0xBE);
|
|
a_emit_byte(a, modrm(3,
|
|
rcode(p->to.type), rcode(p->from.type)));
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVSBQ shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_MOVB:
|
|
/* MOV r/m8, r8 — 88 /r. No REX.W. emit_rex8 forces
|
|
* the bare REX so SI/DI/BP/SP name SIL/DIL/BPL/SPL,
|
|
* never AH/CH/DH/BH. */
|
|
if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type == D_INDIR) {
|
|
emit_rex8(a, rhi(p->from.type), rhi(p->to.reg),
|
|
p->from.type);
|
|
a_emit_byte(a, 0x88);
|
|
emit_modrm_mem(a, rcode(p->from.type),
|
|
p->to.reg, p->to.offset);
|
|
} else if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex8(a, rhi(p->to.type), rhi(p->from.reg),
|
|
p->to.type);
|
|
a_emit_byte(a, 0x8A); /* MOV r8, r/m8 */
|
|
emit_modrm_mem(a, rcode(p->to.type),
|
|
p->from.reg, p->from.offset);
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVB shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_MOVZBQ:
|
|
/* MOVZX r64, r/m8 — 0F B6 /r with REX.W */
|
|
if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 1);
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, 0xB6);
|
|
emit_modrm_mem(a, rcode(p->to.type),
|
|
p->from.reg, p->from.offset);
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVZBQ shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_MOVL:
|
|
/* MOV r/m32, r32 (89 /r) and MOV r32, r/m32 (8B /r),
|
|
* both without REX.W. The CPU zero-extends 32-bit ops
|
|
* into the 64-bit reg, so reads of u32 fields are safe.
|
|
* Sign-extension lives in MOVSXD. */
|
|
if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type == D_INDIR) {
|
|
emit_rex(a, rhi(p->from.type), rhi(p->to.reg), 0);
|
|
a_emit_byte(a, 0x89);
|
|
emit_modrm_mem(a, rcode(p->from.type),
|
|
p->to.reg, p->to.offset);
|
|
} else if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 0);
|
|
a_emit_byte(a, 0x8B);
|
|
emit_modrm_mem(a, rcode(p->to.type),
|
|
p->from.reg, p->from.offset);
|
|
} else if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->from.type), rhi(p->to.type), 0);
|
|
a_emit_byte(a, 0x89);
|
|
a_emit_byte(a, modrm(3,
|
|
rcode(p->from.type), rcode(p->to.type)));
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVL shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_MOVSXD:
|
|
/* MOVSXD r64, r/m32 — 63 /r with REX.W */
|
|
if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 1);
|
|
a_emit_byte(a, 0x63);
|
|
emit_modrm_mem(a, rcode(p->to.type),
|
|
p->from.reg, p->from.offset);
|
|
} else if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.type), 1);
|
|
a_emit_byte(a, 0x63);
|
|
a_emit_byte(a, modrm(3,
|
|
rcode(p->to.type), rcode(p->from.type)));
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVSXD shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_MOVSD:
|
|
/* xmm←mem (load): F2 0F 10 /r */
|
|
/* xmm←xmm: F2 0F 10 /r */
|
|
/* mem←xmm (store):F2 0F 11 /r */
|
|
if (is_xmm(p->from.type) && is_xmm(p->to.type)) {
|
|
sse_rr(a, 0xF2, 0x10, p->to.type, p->from.type);
|
|
} else if (p->from.type == D_INDIR && is_xmm(p->to.type)) {
|
|
sse_mr_load(a, 0xF2, 0x10, p->to.type,
|
|
p->from.reg, p->from.offset);
|
|
} else if (is_xmm(p->from.type) && p->to.type == D_INDIR) {
|
|
sse_mr_load(a, 0xF2, 0x11, p->from.type,
|
|
p->to.reg, p->to.offset);
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVSD shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_ADDSD:
|
|
sse_rr(a, 0xF2, 0x58, p->to.type, p->from.type);
|
|
break;
|
|
case A_SUBSD:
|
|
sse_rr(a, 0xF2, 0x5C, p->to.type, p->from.type);
|
|
break;
|
|
case A_MULSD:
|
|
sse_rr(a, 0xF2, 0x59, p->to.type, p->from.type);
|
|
break;
|
|
case A_DIVSD:
|
|
sse_rr(a, 0xF2, 0x5E, p->to.type, p->from.type);
|
|
break;
|
|
case A_UCOMISD:
|
|
sse_rr(a, 0x66, 0x2E, p->to.type, p->from.type);
|
|
break;
|
|
case A_CVTTSD2SI:
|
|
/* int_reg ← xmm: F2 REX.W 0F 2C /r ; reg=int rm=xmm */
|
|
sse_rr_w(a, 0xF2, 0x2C, p->to.type, p->from.type);
|
|
break;
|
|
case A_CVTSI2SD:
|
|
/* xmm ← int_reg: F2 REX.W 0F 2A /r ; reg=xmm rm=int */
|
|
sse_rr_w(a, 0xF2, 0x2A, p->to.type, p->from.type);
|
|
break;
|
|
case A_MOVSS:
|
|
if (is_xmm(p->from.type) && is_xmm(p->to.type)) {
|
|
sse_rr(a, 0xF3, 0x10, p->to.type, p->from.type);
|
|
} else if (p->from.type == D_INDIR && is_xmm(p->to.type)) {
|
|
sse_mr_load(a, 0xF3, 0x10, p->to.type,
|
|
p->from.reg, p->from.offset);
|
|
} else if (is_xmm(p->from.type) && p->to.type == D_INDIR) {
|
|
sse_mr_load(a, 0xF3, 0x11, p->from.type,
|
|
p->to.reg, p->to.offset);
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported MOVSS shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_ADDSS:
|
|
sse_rr(a, 0xF3, 0x58, p->to.type, p->from.type); break;
|
|
case A_SUBSS:
|
|
sse_rr(a, 0xF3, 0x5C, p->to.type, p->from.type); break;
|
|
case A_MULSS:
|
|
sse_rr(a, 0xF3, 0x59, p->to.type, p->from.type); break;
|
|
case A_DIVSS:
|
|
sse_rr(a, 0xF3, 0x5E, p->to.type, p->from.type); break;
|
|
case A_UCOMISS:
|
|
sse_rr(a, 0x00, 0x2E, p->to.type, p->from.type); break;
|
|
case A_CVTTSS2SI:
|
|
sse_rr_w(a, 0xF3, 0x2C, p->to.type, p->from.type); break;
|
|
case A_CVTSI2SS:
|
|
sse_rr_w(a, 0xF3, 0x2A, p->to.type, p->from.type); break;
|
|
case A_CVTSD2SS:
|
|
/* xmm←xmm: F2 0F 5A /r ; reg=dst rm=src */
|
|
sse_rr(a, 0xF2, 0x5A, p->to.type, p->from.type); break;
|
|
case A_CVTSS2SD:
|
|
sse_rr(a, 0xF3, 0x5A, p->to.type, p->from.type); break;
|
|
case A_ADDQ:
|
|
if (p->from.type == D_CONST
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15)
|
|
encode_ri_imm32(a, 0x81, 0, p->to.type, (i32)p->from.offset);
|
|
else if (p->from.type == D_CONST && p->to.type == D_INDIR) {
|
|
/* ADD r/m64, imm32 — 81 /0 (REX.W) */
|
|
emit_rex(a, 0, rhi(p->to.reg), 1);
|
|
a_emit_byte(a, 0x81);
|
|
emit_modrm_mem(a, 0, p->to.reg, p->to.offset);
|
|
a_emit_u32(a, (u32)(i32)p->from.offset);
|
|
} else if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type == D_INDIR)
|
|
encode_rm(a, 0x01, p->from.type, p->to.reg, p->to.offset);
|
|
else if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15)
|
|
encode_mr(a, 0x03, p->to.type, p->from.reg, p->from.offset);
|
|
else
|
|
encode_rr(a, 0x01, p->from.type, p->to.type);
|
|
break;
|
|
case A_SUBQ:
|
|
if (p->from.type == D_CONST
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15)
|
|
encode_ri_imm32(a, 0x81, 5, p->to.type, (i32)p->from.offset);
|
|
else if (p->from.type == D_CONST && p->to.type == D_INDIR) {
|
|
emit_rex(a, 0, rhi(p->to.reg), 1);
|
|
a_emit_byte(a, 0x81);
|
|
emit_modrm_mem(a, 5, p->to.reg, p->to.offset);
|
|
a_emit_u32(a, (u32)(i32)p->from.offset);
|
|
} else if (p->from.type >= D_AX && p->from.type <= D_R15
|
|
&& p->to.type == D_INDIR)
|
|
encode_rm(a, 0x29, p->from.type, p->to.reg, p->to.offset);
|
|
else if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15)
|
|
encode_mr(a, 0x2B, p->to.type, p->from.reg, p->from.offset);
|
|
else
|
|
encode_rr(a, 0x29, p->from.type, p->to.type);
|
|
break;
|
|
case A_ANDQ:
|
|
/* AND r/m64, imm32 — 81 /4 (REX.W). Without the
|
|
* D_CONST path the rr encoder would silently emit
|
|
* a 0x21 with garbage reg fields. */
|
|
if (p->from.type == D_CONST
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15)
|
|
encode_ri_imm32(a, 0x81, 4, p->to.type, (i32)p->from.offset);
|
|
else
|
|
encode_rr(a, 0x21, p->from.type, p->to.type);
|
|
break;
|
|
case A_ORQ:
|
|
/* OR r/m64, imm32 — 81 /1 (REX.W). Mirrors ANDQ. */
|
|
if (p->from.type == D_CONST
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15)
|
|
encode_ri_imm32(a, 0x81, 1, p->to.type, (i32)p->from.offset);
|
|
else
|
|
encode_rr(a, 0x09, p->from.type, p->to.type);
|
|
break;
|
|
case A_XORQ:
|
|
if (p->from.type == D_CONST
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15)
|
|
encode_ri_imm32(a, 0x81, 6, p->to.type, (i32)p->from.offset);
|
|
else
|
|
encode_rr(a, 0x31, p->from.type, p->to.type);
|
|
break;
|
|
case A_IMULQ:
|
|
emit_rex(a, rhi(p->to.type), rhi(p->from.type), 1);
|
|
a_emit_byte(a, 0x0F); a_emit_byte(a, 0xAF);
|
|
a_emit_byte(a, modrm(3, rcode(p->to.type), rcode(p->from.type)));
|
|
break;
|
|
case A_SHLQ:
|
|
encode_unary(a, 0xD3, 4, p->to.type); break;
|
|
case A_SHRQ:
|
|
encode_unary(a, 0xD3, 5, p->to.type); break;
|
|
case A_SARQ:
|
|
/* SAR r/m64, CL: REX.W + D3 /7 (arithmetic right shift,
|
|
* sign-extends MSB — distinct from SHR D3 /5 which
|
|
* injects zeros). Same encoding shape; only modrm.reg
|
|
* digit differs. #136. */
|
|
encode_unary(a, 0xD3, 7, p->to.type); break;
|
|
case A_CMPQ:
|
|
if (p->from.type == D_CONST && p->to.type >= D_AX && p->to.type <= D_R15)
|
|
encode_ri_imm32(a, 0x81, 7, p->to.type, (i32)p->from.offset);
|
|
else
|
|
encode_rr(a, 0x39, p->from.type, p->to.type);
|
|
break;
|
|
case A_LEAQ:
|
|
if (p->from.type == D_INDIR
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
encode_mr(a, 0x8D, p->to.type, p->from.reg, p->from.offset);
|
|
} else if (p->from.type == D_EXTERN
|
|
&& p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
/* RIP-relative: 48 8D /r mod=00 rm=5 disp32 */
|
|
emit_rex(a, rhi(p->to.type), 0, 1);
|
|
a_emit_byte(a, 0x8D);
|
|
a_emit_byte(a, modrm(0, rcode(p->to.type), 5));
|
|
u64 reloff = a->textlen;
|
|
a_emit_u32(a, 0);
|
|
Asym *s = a_intern(a, p->from.sym);
|
|
/* R_X86_64_PC32 (2) with addend -4 */
|
|
a_addreloc(a, reloff, 2, s, -4);
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported LEAQ shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_CALL:
|
|
if (p->to.type == D_EXTERN) {
|
|
a_emit_byte(a, 0xE8);
|
|
u64 reloff = a->textlen;
|
|
a_emit_u32(a, 0);
|
|
Asym *s = a_intern(a, p->to.sym);
|
|
/* R_X86_64_PLT32 (4); addend -4 */
|
|
a_addreloc(a, reloff, 4, s, -4);
|
|
} else if (p->to.type == D_BRANCH) {
|
|
a_emit_byte(a, 0xE8);
|
|
add_fixup(a->textlen, p->to.sym);
|
|
a_emit_u32(a, 0);
|
|
} else if (p->to.type >= D_AX && p->to.type <= D_R15) {
|
|
if (rhi(p->to.type)) a_emit_byte(a, 0x41);
|
|
a_emit_byte(a, 0xFF);
|
|
a_emit_byte(a, modrm(3, 2, rcode(p->to.type)));
|
|
} else {
|
|
fprintf(stderr, "w6a: line %d: unsupported CALL shape\n", p->line);
|
|
a->errs++;
|
|
}
|
|
break;
|
|
case A_JMP:
|
|
a_emit_byte(a, 0xE9);
|
|
add_fixup(a->textlen, p->to.sym);
|
|
a_emit_u32(a, 0);
|
|
break;
|
|
case A_JE: case A_JNE: case A_JL: case A_JLE:
|
|
case A_JG: case A_JGE: case A_JB: case A_JBE:
|
|
case A_JA: case A_JAE: case A_JZ: case A_JNZ:
|
|
case A_JP: {
|
|
u8 cc = 0;
|
|
switch (p->as) {
|
|
case A_JE: case A_JZ: cc = 0x84; break;
|
|
case A_JNE: case A_JNZ: cc = 0x85; break;
|
|
case A_JL: cc = 0x8C; break;
|
|
case A_JLE: cc = 0x8E; break;
|
|
case A_JG: cc = 0x8F; break;
|
|
case A_JGE: cc = 0x8D; break;
|
|
case A_JB: cc = 0x82; break;
|
|
case A_JBE: cc = 0x86; break;
|
|
case A_JA: cc = 0x87; break;
|
|
case A_JAE: cc = 0x83; break;
|
|
case A_JP: cc = 0x8A; break;
|
|
default: break;
|
|
}
|
|
a_emit_byte(a, 0x0F);
|
|
a_emit_byte(a, cc);
|
|
add_fixup(a->textlen, p->to.sym);
|
|
a_emit_u32(a, 0);
|
|
break;
|
|
}
|
|
default:
|
|
fprintf(stderr, "w6a: unsupported opcode %d on line %d\n", p->as, p->line);
|
|
a->errs++;
|
|
}
|
|
}
|
|
|
|
for (Fixup *f = fixups; f; f = f->next) {
|
|
if (!label_defined(a, f->label)) {
|
|
fprintf(stderr, "w6a: undefined label '%s'\n", f->label);
|
|
a->errs++;
|
|
continue;
|
|
}
|
|
u64 target = resolve_label(a, f->label);
|
|
i64 rel = (i64)target - ((i64)f->off + 4);
|
|
i32 rel32 = (i32)rel;
|
|
a->text[f->off + 0] = (u8)(rel32 & 0xff);
|
|
a->text[f->off + 1] = (u8)((rel32 >> 8) & 0xff);
|
|
a->text[f->off + 2] = (u8)((rel32 >> 16) & 0xff);
|
|
a->text[f->off + 3] = (u8)((rel32 >> 24) & 0xff);
|
|
}
|
|
return a->errs;
|
|
}
|