mem.ww was retired in Phase-0; these two imports survived only because the driver silently skipped unresolvable imports. Provably dead: no mem module exists and both units build identically without them. Removal precedes the loud driver, which would otherwise fatal on them.
849 lines
26 KiB
Plaintext
849 lines
26 KiB
Plaintext
// selfhost/cmd/w6a/asm.ww — port of cmd/w6a/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 matches what w6c emits — see cmd/w6a/asm.c for the
|
|
// authoritative list. Helpers (rcode/rhi/modrm/emitrex etc.) are
|
|
// fully ported; encode itself is still a stub pending the full
|
|
// switch over A_*.
|
|
|
|
package w6a;
|
|
|
|
import os;
|
|
import rt;
|
|
import opcodes;
|
|
import strings;
|
|
|
|
// ---- text buffer growth ------------------------------------------------
|
|
|
|
export fn emitbyte(a: *asm_, b: u8) void = {
|
|
if (a.textlen + 1u64 > a.textcap) {
|
|
let nc: u64 = a.textcap;
|
|
if (nc == 0u64) { nc = 4096u64; };
|
|
nc = nc * 2u64;
|
|
let nb: []u8 = alloc([], nc)!;
|
|
let i: u64 = 0u64;
|
|
for (i < a.textlen) { nb[i] = a.text[i]; i += 1u64; };
|
|
a.text = nb.ptr;
|
|
a.textcap = nc;
|
|
};
|
|
a.text[a.textlen] = b;
|
|
a.textlen += 1u64;
|
|
};
|
|
|
|
export fn emitu32(a: *asm_, v: u32) void = {
|
|
emitbyte(a, (v & 255u32): u8);
|
|
emitbyte(a, ((v >> 8u32) & 255u32): u8);
|
|
emitbyte(a, ((v >> 16u32) & 255u32): u8);
|
|
emitbyte(a, ((v >> 24u32) & 255u32): u8);
|
|
};
|
|
|
|
export fn addreloc(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = {
|
|
let r: *areloc = alloc(areloc { off = off, section = 0, kind = kind, asy = s, addend = add, rnext = 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.ww
|
|
// separates these into .rela.data when emitting the .o.
|
|
export fn addrelocdata(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = {
|
|
let r: *areloc = alloc(areloc { off = off, section = 1, kind = kind, asy = s, addend = add, rnext = a.relocs })!;
|
|
a.relocs = r;
|
|
};
|
|
|
|
// Append one byte to the writable .data buffer. Mirrors emitbyte
|
|
// but targets a.data instead of a.text.
|
|
export fn emitdatabyte(a: *asm_, b: u8) void = {
|
|
if (a.datalen + 1u64 > a.datacap) {
|
|
let nc: u64 = a.datacap;
|
|
if (nc == 0u64) { nc = 256u64; };
|
|
nc = nc * 2u64;
|
|
let nb: []u8 = alloc([], nc)!;
|
|
let i: u64 = 0u64;
|
|
for (i < a.datalen) { nb[i] = a.data[i]; i += 1u64; };
|
|
a.data = nb.ptr;
|
|
a.datacap = nc;
|
|
};
|
|
a.data[a.datalen] = b;
|
|
a.datalen += 1u64;
|
|
};
|
|
|
|
// ---- register codes ----------------------------------------------------
|
|
|
|
// Low 3 bits of register encoding.
|
|
fn rcode(r: i32) i32 = {
|
|
if (r == D_AX) { return 0; }; if (r == D_CX) { return 1; };
|
|
if (r == D_DX) { return 2; }; if (r == D_BX) { return 3; };
|
|
if (r == D_SP) { return 4; }; if (r == D_BP) { return 5; };
|
|
if (r == D_SI) { return 6; }; if (r == D_DI) { return 7; };
|
|
if (r == D_R8) { return 0; }; if (r == D_R9) { return 1; };
|
|
if (r == D_R10) { return 2; }; if (r == D_R11) { return 3; };
|
|
if (r == D_R12) { return 4; }; if (r == D_R13) { return 5; };
|
|
if (r == D_R14) { return 6; }; if (r == D_R15) { return 7; };
|
|
if (r == D_X0) { return 0; }; if (r == D_X1) { return 1; };
|
|
if (r == D_X2) { return 2; }; if (r == D_X3) { return 3; };
|
|
if (r == D_X4) { return 4; }; if (r == D_X5) { return 5; };
|
|
if (r == D_X6) { return 6; }; if (r == D_X7) { return 7; };
|
|
if (r == D_X8) { return 0; }; if (r == D_X9) { return 1; };
|
|
if (r == D_X10) { return 2; }; if (r == D_X11) { return 3; };
|
|
if (r == D_X12) { return 4; }; if (r == D_X13) { return 5; };
|
|
if (r == D_X14) { return 6; }; if (r == D_X15) { return 7; };
|
|
return 0;
|
|
};
|
|
|
|
// 1 if r needs the REX high bit (R8..R15 or X8..X15).
|
|
fn rhi(r: i32) i32 = {
|
|
if (r >= D_R8) { if (r <= D_R15) { return 1; }; };
|
|
if (r >= D_X8) { if (r <= D_X15) { return 1; }; };
|
|
return 0;
|
|
};
|
|
|
|
fn isxmm(r: i32) bool = {
|
|
if (r >= D_X0) { if (r <= D_X15) { return true; }; };
|
|
return false;
|
|
};
|
|
|
|
// ModR/M byte builder.
|
|
fn modrmbyte(mod: i32, reg: i32, rm: i32) u8 = {
|
|
return (((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)): u8;
|
|
};
|
|
|
|
// REX prefix; W=1 for 64-bit operand size.
|
|
fn emitrex(a: *asm_, regbit: i32, rmbit: i32, w: i32) void = {
|
|
let b: u8 = 64u8; // 0x40
|
|
if (w != 0) { b = b | 8u8; };
|
|
if (regbit != 0) { b = b | 4u8; };
|
|
if (rmbit != 0) { b = b | 1u8; };
|
|
if (b != 64u8) { emitbyte(a, b); }
|
|
else { if (w != 0) { emitbyte(a, b); }; };
|
|
};
|
|
|
|
// ModR/M + (optional) SIB + displacement for [base+disp].
|
|
// Special-cases SP (needs SIB) and BP (forces explicit disp).
|
|
fn emitmodrmmem(a: *asm_, regfield: i32, base: i32, disp: i64) void = {
|
|
let rm: i32 = rcode(base);
|
|
let needsib: bool = (rm == 4);
|
|
let forceddisp: bool = false;
|
|
if (rm == 5) { if (disp == 0i64) { forceddisp = true; }; };
|
|
|
|
let mod: i32 = 2;
|
|
if (disp == 0i64) {
|
|
if (!forceddisp) { mod = 0; }
|
|
else { mod = 1; };
|
|
} else {
|
|
if (disp >= -128i64) { if (disp <= 127i64) { mod = 1; }; };
|
|
};
|
|
|
|
emitbyte(a, modrmbyte(mod, regfield, rm));
|
|
if (needsib) {
|
|
emitbyte(a, 36u8); // 0x24: scale=0 idx=4(none) base=4
|
|
};
|
|
if (mod == 1) {
|
|
emitbyte(a, (disp: u64 & 255u64): u8);
|
|
} else { if (mod == 2) {
|
|
emitu32(a, disp: u32);
|
|
};};
|
|
};
|
|
|
|
// reg→reg "src, dst" generic encoding (89 /r, 01 /r, etc.).
|
|
fn encoderr(a: *asm_, opcode: u8, src: i32, dst: i32) void = {
|
|
emitrex(a, rhi(src), rhi(dst), 1);
|
|
emitbyte(a, opcode);
|
|
emitbyte(a, modrmbyte(3, rcode(src), rcode(dst)));
|
|
};
|
|
|
|
// reg→mem(base, disp) (e.g. MOVQ src reg into mem; opcode = 0x89).
|
|
fn encoderm(a: *asm_, opcode: u8, srcreg: i32, base: i32, disp: i64) void = {
|
|
emitrex(a, rhi(srcreg), rhi(base), 1);
|
|
emitbyte(a, opcode);
|
|
emitmodrmmem(a, rcode(srcreg), base, disp);
|
|
};
|
|
|
|
// mem(base, disp) → reg (e.g. MOVQ mem into reg; opcode = 0x8B).
|
|
fn encodemr(a: *asm_, opcode: u8, dstreg: i32, base: i32, disp: i64) void = {
|
|
emitrex(a, rhi(dstreg), rhi(base), 1);
|
|
emitbyte(a, opcode);
|
|
emitmodrmmem(a, rcode(dstreg), base, disp);
|
|
};
|
|
|
|
// OPCODE /n imm32 reg form (e.g. ADDQ $imm, reg).
|
|
fn encoderiimm32(a: *asm_, opcode: u8, subop: i32, dst: i32, imm: i32) void = {
|
|
emitrex(a, 0, rhi(dst), 1);
|
|
emitbyte(a, opcode);
|
|
emitbyte(a, modrmbyte(3, subop, rcode(dst)));
|
|
emitu32(a, imm: u32);
|
|
};
|
|
|
|
// Unary on reg: F7 /n reg, etc.
|
|
fn encodeunary(a: *asm_, opcode: u8, subop: i32, dst: i32) void = {
|
|
emitrex(a, 0, rhi(dst), 1);
|
|
emitbyte(a, opcode);
|
|
emitbyte(a, modrmbyte(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.
|
|
fn sserr(a: *asm_, prefix: u8, op2: u8, regop: i32, rmop: i32) void = {
|
|
if (prefix != 0u8) { emitbyte(a, prefix); };
|
|
emitrex(a, rhi(regop), rhi(rmop), 0);
|
|
emitbyte(a, 15u8); // 0x0F
|
|
emitbyte(a, op2);
|
|
emitbyte(a, modrmbyte(3, rcode(regop), rcode(rmop)));
|
|
};
|
|
|
|
fn ssemrload(a: *asm_, prefix: u8, op2: u8, regop: i32, base: i32, disp: i64) void = {
|
|
if (prefix != 0u8) { emitbyte(a, prefix); };
|
|
emitrex(a, rhi(regop), rhi(base), 0);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, op2);
|
|
emitmodrmmem(a, rcode(regop), base, disp);
|
|
};
|
|
|
|
// REX.W variant of sse_rr (CVTTSD2SI / CVTSI2SD).
|
|
fn sserrw(a: *asm_, prefix: u8, op2: u8, regop: i32, rmop: i32) void = {
|
|
if (prefix != 0u8) { emitbyte(a, prefix); };
|
|
emitrex(a, rhi(regop), rhi(rmop), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, op2);
|
|
emitbyte(a, modrmbyte(3, rcode(regop), rcode(rmop)));
|
|
};
|
|
|
|
// ---- label resolution / fixups ----------------------------------------
|
|
|
|
fn resolvelabel(a: *asm_, name: str) u64 = {
|
|
let s: *asym = a.syms;
|
|
for (s != nil) {
|
|
if (s.defined != 0) { if (strings.compare(s.name, name) == 0) { return s.addr; }; };
|
|
s = s.snext;
|
|
};
|
|
return 0u64;
|
|
};
|
|
|
|
fn labeldefined(a: *asm_, name: str) bool = {
|
|
let s: *asym = a.syms;
|
|
for (s != nil) {
|
|
if (s.defined != 0) { if (strings.compare(s.name, name) == 0) { return true; }; };
|
|
s = s.snext;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// ---- fixup helper -----------------------------------------------------
|
|
|
|
fn addfixup(a: *asm_, off: u64, label: str) void = {
|
|
let f: *afixup = alloc(afixup { off = off, label = label, fnext = a.fixups })!;
|
|
a.fixups = f;
|
|
};
|
|
|
|
fn isgpr(t: i32) bool = {
|
|
if (t >= D_AX) { if (t <= D_R15) { return true; }; };
|
|
return false;
|
|
};
|
|
|
|
// `intern` lives in parse.ww — flat-scope concat lets us call it
|
|
// directly without an @symbol declaration here.
|
|
|
|
// ---- encode ----------------------------------------------------------
|
|
|
|
export fn encode(a: *asm_) i32 = {
|
|
let p: *aprog = a.head;
|
|
for (p != nil) {
|
|
// Define any pending label at the current PC.
|
|
if (p.label.len > 0) {
|
|
let s: *asym = intern(a, p.label);
|
|
s.defined = 1;
|
|
s.istext = 1;
|
|
s.addr = a.textlen;
|
|
};
|
|
let op: i32 = p.as_;
|
|
|
|
if (op == A_NOP) {
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_TEXT) {
|
|
let s: *asym = intern(a, p.to.asym);
|
|
s.defined = 1;
|
|
s.istext = 1;
|
|
s.isglobal = 1;
|
|
s.addr = a.textlen;
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_DATA) {
|
|
let s: *asym = intern(a, p.to.asym);
|
|
s.defined = 1;
|
|
s.istext = 1;
|
|
s.isglobal = 1;
|
|
s.addr = a.textlen;
|
|
let i: u64 = 0u64;
|
|
for (i < p.nbytes) { emitbyte(a, p.bytes[i]); i += 1u64; };
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_DATAW) {
|
|
// Writable variant: bytes go into .data instead of
|
|
// .text. obj.ww emits the extra section conditionally
|
|
// on datalen > 0 so .o output stays byte-identical
|
|
// for inputs that don't use DATAW.
|
|
let s: *asym = intern(a, p.to.asym);
|
|
s.defined = 1;
|
|
s.isdata = 1;
|
|
s.isglobal = 1;
|
|
s.addr = a.datalen;
|
|
let i: u64 = 0u64;
|
|
for (i < p.nbytes) { emitdatabyte(a, p.bytes[i]); i += 1u64; };
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_DATAR) {
|
|
// DATAR slot+off(SB), target(SB) — record an
|
|
// R_X86_64_64 relocation at slot+off in .data
|
|
// pointing at target. Slot must already be defined
|
|
// by a prior DATAW.
|
|
let holder: *asym = intern(a, p.from.asym);
|
|
if (holder.defined == 0) {
|
|
p = p.link; continue;
|
|
};
|
|
if (holder.isdata == 0) {
|
|
p = p.link; continue;
|
|
};
|
|
let target: *asym = intern(a, p.to.asym);
|
|
let reloff: u64 = holder.addr + p.from.offset: u64;
|
|
addrelocdata(a, reloff, 1 /* R_X86_64_64 */,
|
|
target, 0i64);
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_RET) {
|
|
emitbyte(a, 195u8); // 0xC3
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_SYSCALL) {
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 5u8);
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_PUSHQ) {
|
|
if (rhi(p.to.atype) != 0) { emitbyte(a, 65u8); }; // 0x41
|
|
emitbyte(a, (80 + rcode(p.to.atype)): u8); // 0x50
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_POPQ) {
|
|
if (rhi(p.to.atype) != 0) { emitbyte(a, 65u8); };
|
|
emitbyte(a, (88 + rcode(p.to.atype)): u8); // 0x58
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_NEGQ) { encodeunary(a, 247u8, 3, p.to.atype); p = p.link; continue; };
|
|
if (op == A_NOTQ) { encodeunary(a, 247u8, 2, p.to.atype); p = p.link; continue; };
|
|
if (op == A_IDIVQ) { encodeunary(a, 247u8, 7, p.to.atype); p = p.link; continue; };
|
|
if (op == A_DIVQ) { encodeunary(a, 247u8, 6, p.to.atype); p = p.link; continue; };
|
|
if (op == A_CQO) {
|
|
emitbyte(a, 72u8); // REX.W (0x48)
|
|
emitbyte(a, 153u8); // 0x99
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
let v: i64 = p.from.offset;
|
|
if (v >= -2147483648i64) { if (v <= 2147483647i64) {
|
|
encoderiimm32(a, 199u8, 0, tt, v: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
// movabs r64, imm64: REX.W B8+rd imm64
|
|
emitrex(a, 0, rhi(tt), 1);
|
|
emitbyte(a, (184 + rcode(tt)): u8);
|
|
let k: i32 = 0;
|
|
for (k < 8) {
|
|
emitbyte(a, ((v: u64 >> (k: u64 * 8u64)) & 255u64): u8);
|
|
k += 1;
|
|
};
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
encoderr(a, 137u8, ft, tt); // 0x89
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
encodemr(a, 139u8, tt, p.from.reg, p.from.offset); // 0x8B
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
encoderm(a, 137u8, ft, p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_CONST) { if (tt == D_INDIR) {
|
|
emitrex(a, 0, rhi(p.to.reg), 1);
|
|
emitbyte(a, 199u8);
|
|
emitmodrmmem(a, 0, p.to.reg, p.to.offset);
|
|
emitu32(a, p.from.offset: u32);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_EXTERN) { if (isgpr(tt)) {
|
|
// RIP-relative load: 48 8B /r mod=00 rm=5 disp32
|
|
emitrex(a, rhi(tt), 0, 1);
|
|
emitbyte(a, 139u8);
|
|
emitbyte(a, modrmbyte(0, rcode(tt), 5));
|
|
let reloff: u64 = a.textlen;
|
|
emitu32(a, 0u32);
|
|
let s: *asym = intern(a, p.from.asym);
|
|
addreloc(a, reloff, 2, s, -4i64);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (tt == D_EXTERN) {
|
|
// RIP-relative store: 48 89 /r mod=00 rm=5 disp32
|
|
emitrex(a, rhi(ft), 0, 1);
|
|
emitbyte(a, 137u8);
|
|
emitbyte(a, modrmbyte(0, rcode(ft), 5));
|
|
let reloff: u64 = a.textlen;
|
|
emitu32(a, 0u32);
|
|
let s: *asym = intern(a, p.to.asym);
|
|
addreloc(a, reloff, 2, s, -4i64);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVQ shape\n".ptr, 27u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVB) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
emitrex(a, rhi(ft), rhi(p.to.reg), 0);
|
|
emitbyte(a, 136u8); // 0x88
|
|
emitmodrmmem(a, rcode(ft), p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 0);
|
|
emitbyte(a, 138u8); // 0x8A
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVB shape\n".ptr, 27u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVW) {
|
|
// 16-bit MOV: 0x66 operand-size prefix + the 32-bit
|
|
// MOV opcodes 0x89 / 0x8B. No REX.W.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
emitbyte(a, 102u8); // 0x66
|
|
emitrex(a, rhi(ft), rhi(p.to.reg), 0);
|
|
emitbyte(a, 137u8); // 0x89
|
|
emitmodrmmem(a, rcode(ft), p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitbyte(a, 102u8); // 0x66
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 0);
|
|
emitbyte(a, 139u8); // 0x8B
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVW shape\n".ptr, 27u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVZWQ) {
|
|
// MOVZX r64, r/m16 — 0F B7 /r with REX.W.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 183u8); // 0xB7
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVZWQ shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVSWQ) {
|
|
// MOVSX r64, r/m16 — 0F BF /r with REX.W.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 191u8); // 0xBF
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(ft), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 191u8); // 0xBF
|
|
emitbyte(a, modrmbyte(3, rcode(tt), rcode(ft)));
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSWQ shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVSBQ) {
|
|
// MOVSX r64, r/m8 — 0F BE /r with REX.W.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 190u8); // 0xBE
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(ft), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 190u8); // 0xBE
|
|
emitbyte(a, modrmbyte(3, rcode(tt), rcode(ft)));
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSBQ shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVZBQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 182u8); // 0xB6
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVZBQ shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVL) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
emitrex(a, rhi(ft), rhi(p.to.reg), 0);
|
|
emitbyte(a, 137u8);
|
|
emitmodrmmem(a, rcode(ft), p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 0);
|
|
emitbyte(a, 139u8);
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(ft), rhi(tt), 0);
|
|
emitbyte(a, 137u8);
|
|
emitbyte(a, modrmbyte(3, rcode(ft), rcode(tt)));
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVL shape\n".ptr, 27u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVSXD) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 99u8); // 0x63
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(ft), 1);
|
|
emitbyte(a, 99u8); // 0x63
|
|
emitbyte(a, modrmbyte(3, rcode(tt), rcode(ft)));
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSXD shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVSD) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isxmm(ft)) { if (isxmm(tt)) {
|
|
sserr(a, 242u8, 16u8, tt, ft);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isxmm(tt)) {
|
|
ssemrload(a, 242u8, 16u8, tt, p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isxmm(ft)) { if (tt == D_INDIR) {
|
|
ssemrload(a, 242u8, 17u8, ft, p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSD shape\n".ptr, 28u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_ADDSD) { sserr(a, 242u8, 88u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_SUBSD) { sserr(a, 242u8, 92u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_MULSD) { sserr(a, 242u8, 89u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_DIVSD) { sserr(a, 242u8, 94u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_UCOMISD) { sserr(a, 102u8, 46u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTTSD2SI) { sserrw(a, 242u8, 44u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTSI2SD) { sserrw(a, 242u8, 42u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
|
|
if (op == A_MOVSS) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isxmm(ft)) { if (isxmm(tt)) {
|
|
sserr(a, 243u8, 16u8, tt, ft); p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isxmm(tt)) {
|
|
ssemrload(a, 243u8, 16u8, tt, p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isxmm(ft)) { if (tt == D_INDIR) {
|
|
ssemrload(a, 243u8, 17u8, ft, p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSS shape\n".ptr, 28u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_ADDSS) { sserr(a, 243u8, 88u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_SUBSS) { sserr(a, 243u8, 92u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_MULSS) { sserr(a, 243u8, 89u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_DIVSS) { sserr(a, 243u8, 94u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_UCOMISS) { sserr(a, 0u8, 46u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTTSS2SI) { sserrw(a, 243u8, 44u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTSI2SS) { sserrw(a, 243u8, 42u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTSD2SS) { sserr(a, 242u8, 90u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTSS2SD) { sserr(a, 243u8, 90u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
|
|
if (op == A_ADDQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 0, tt, p.from.offset: i32); // 0x81
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_CONST) { if (tt == D_INDIR) {
|
|
emitrex(a, 0, rhi(p.to.reg), 1);
|
|
emitbyte(a, 129u8);
|
|
emitmodrmmem(a, 0, p.to.reg, p.to.offset);
|
|
emitu32(a, p.from.offset: u32);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
encoderm(a, 1u8, ft, p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
encodemr(a, 3u8, tt, p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 1u8, ft, tt);
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_SUBQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 5, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_CONST) { if (tt == D_INDIR) {
|
|
emitrex(a, 0, rhi(p.to.reg), 1);
|
|
emitbyte(a, 129u8);
|
|
emitmodrmmem(a, 5, p.to.reg, p.to.offset);
|
|
emitu32(a, p.from.offset: u32);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
encoderm(a, 41u8, ft, p.to.reg, p.to.offset); // 0x29
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
encodemr(a, 43u8, tt, p.from.reg, p.from.offset); // 0x2B
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 41u8, ft, tt);
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_ANDQ) {
|
|
// AND r/m64, imm32 — 0x81 /4 (REX.W). Without the
|
|
// D_CONST path encoderr would silently emit 0x21
|
|
// with garbage reg fields.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 4, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 33u8, ft, tt); // 0x21
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_ORQ) {
|
|
// OR r/m64, imm32 — 0x81 /1 (REX.W). Mirrors ANDQ.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 1, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 9u8, ft, tt); // 0x09
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_XORQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 6, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 49u8, ft, tt); // 0x31
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_IMULQ) {
|
|
emitrex(a, rhi(p.to.atype), rhi(p.from.atype), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 175u8); // 0xAF
|
|
emitbyte(a, modrmbyte(3, rcode(p.to.atype), rcode(p.from.atype)));
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_SHLQ) { encodeunary(a, 211u8, 4, p.to.atype); p = p.link; continue; }; // 0xD3
|
|
if (op == A_SHRQ) { encodeunary(a, 211u8, 5, p.to.atype); p = p.link; continue; };
|
|
// #136: SAR r/m64, CL — REX.W + D3 /7 (arithmetic right
|
|
// shift, sign-extends MSB; cstage twin cmd/w6a/asm.c).
|
|
if (op == A_SARQ) { encodeunary(a, 211u8, 7, p.to.atype); p = p.link; continue; };
|
|
if (op == A_CMPQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 7, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 57u8, ft, tt); // 0x39
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_LEAQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
encodemr(a, 141u8, tt, p.from.reg, p.from.offset); // 0x8D
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_EXTERN) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), 0, 1);
|
|
emitbyte(a, 141u8);
|
|
emitbyte(a, modrmbyte(0, rcode(tt), 5));
|
|
let reloff: u64 = a.textlen;
|
|
emitu32(a, 0u32);
|
|
let s: *asym = intern(a, p.from.asym);
|
|
addreloc(a, reloff, 2, s, -4i64);
|
|
p = p.link; continue;
|
|
};};
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_CALL) {
|
|
let tt: i32 = p.to.atype;
|
|
if (tt == D_EXTERN) {
|
|
emitbyte(a, 232u8); // 0xE8
|
|
let reloff: u64 = a.textlen;
|
|
emitu32(a, 0u32);
|
|
let s: *asym = intern(a, p.to.asym);
|
|
addreloc(a, reloff, 4, s, -4i64);
|
|
p = p.link; continue;
|
|
};
|
|
if (tt == D_BRANCH) {
|
|
emitbyte(a, 232u8);
|
|
addfixup(a, a.textlen, p.to.asym);
|
|
emitu32(a, 0u32);
|
|
p = p.link; continue;
|
|
};
|
|
if (isgpr(tt)) {
|
|
if (rhi(tt) != 0) { emitbyte(a, 65u8); };
|
|
emitbyte(a, 255u8); // 0xFF
|
|
emitbyte(a, modrmbyte(3, 2, rcode(tt)));
|
|
p = p.link; continue;
|
|
};
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_JMP) {
|
|
emitbyte(a, 233u8); // 0xE9
|
|
addfixup(a, a.textlen, p.to.asym);
|
|
emitu32(a, 0u32);
|
|
p = p.link; continue;
|
|
};
|
|
|
|
// Conditional jumps. 0x0F + cc + rel32.
|
|
let cc: u8 = 0u8;
|
|
let isjcc: bool = true;
|
|
switch (op) {
|
|
case A_JE: cc = 132u8; // 0x84
|
|
case A_JZ: cc = 132u8;
|
|
case A_JNE: cc = 133u8;
|
|
case A_JNZ: cc = 133u8;
|
|
case A_JL: cc = 140u8;
|
|
case A_JLE: cc = 142u8;
|
|
case A_JG: cc = 143u8;
|
|
case A_JGE: cc = 141u8;
|
|
case A_JB: cc = 130u8;
|
|
case A_JBE: cc = 134u8;
|
|
case A_JA: cc = 135u8;
|
|
case A_JAE: cc = 131u8;
|
|
case A_JP: cc = 138u8; // 0x8A, UCOMISD unordered (#97)
|
|
case: isjcc = false;
|
|
};
|
|
if (isjcc) {
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, cc);
|
|
addfixup(a, a.textlen, p.to.asym);
|
|
emitu32(a, 0u32);
|
|
p = p.link; continue;
|
|
};
|
|
|
|
os.write(2, "w6a: unsupported opcode\n".ptr, 23u64);
|
|
a.errs += 1;
|
|
p = p.link;
|
|
};
|
|
|
|
// Second pass: patch fixups (forward label refs).
|
|
let f: *afixup = a.fixups;
|
|
for (f != nil) {
|
|
if (!labeldefined(a, f.label)) {
|
|
os.write(2, "w6a: undefined label '".ptr, 21u64);
|
|
let lbl: str = f.label;
|
|
os.write(2, lbl.ptr, lbl.len: u64);
|
|
os.write(2, "'\n".ptr, 2u64);
|
|
a.errs += 1;
|
|
f = f.fnext;
|
|
continue;
|
|
};
|
|
let target: u64 = resolvelabel(a, f.label);
|
|
let rel: i64 = target: i64 - (f.off: i64 + 4i64);
|
|
let rel32: u32 = rel: u32;
|
|
a.text[f.off] = (rel32 & 255u32): u8;
|
|
a.text[f.off + 1u64] = ((rel32 >> 8u32) & 255u32): u8;
|
|
a.text[f.off + 2u64] = ((rel32 >> 16u32) & 255u32): u8;
|
|
a.text[f.off + 3u64] = ((rel32 >> 24u32) & 255u32): u8;
|
|
f = f.fnext;
|
|
};
|
|
return a.errs;
|
|
};
|