/* * asm.c — encode the parsed Aprog list into amd64 machine bytes, * appending to Asm.text. Relocations for CALL/branch targets that * resolve to externals are queued in Asm.relocs. * * 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 #include #include 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; } /* ------ register codes ------------------------------------------- */ /* 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; } /* ModR/M byte */ static u8 modrm(int mod, int reg, int rm) { return (u8)(((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)); } /* emit REX with W=1 plus optional R/B for high regs */ 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); } /* encode mod/disp for [base+disp]; returns 0 on ok. * 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))); } /* MOVQ src reg → mem(base, disp). opcode = 0x89 */ 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); } /* MOVQ mem(base, disp) → reg. opcode = 0x8B */ 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_mr_load 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))); } /* ------ second-pass helper: resolve labels to addresses ---------- */ 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; } /* ------ first pass: encode ---------------------------------------- */ /* 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; /* current TEXT name */ (void)cur_text; for (Aprog *p = a->head; p; p = p->link) { /* Define any pending label at the current PC */ 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. We always emit REX * to allow access to SIL/DIL/BPL/SPL. */ 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, 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_rex(a, rhi(p->to.type), rhi(p->from.reg), 0); 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); } 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) { /* local call to a label */ 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))); } 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++; } } /* second pass: patch fixups */ 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; }