Add SAR/SARQ to both assemblers' opcode tables (cstage cmd/w6a +
wwstage selfhost/cmd/w6a) — REX.W + D3 /7, parallel to SHR's D3 /5.
Encoding is the unary-on-CL form (SAR r/m64, CL), the only variant
the cgen emits today. cstage cgen + wwstage cgen sweep all 12 SHRQ
emission sites (6 per stage) so signed RSHIFT and signed RSHIFTEQ
route through SARQ (arithmetic, sign-extends MSB) instead of SHRQ
(logical, zero-fill). Pre-fix `let i: i32 = -200; i >>= 2;`
produced 0x3FFFFFCE (1073741774) instead of -50; cs==ww held because
BOTH stages emitted SHRQ, so the 990-997 byte-id gates were
gate-blind to this silent miscompile.
Sites covered (per stage 6, same shape in both):
- plain TK_RSHIFT (cgbin / N_BIN ordered binop) — derives unsignd
from operand types via type_isunsigned / nodeisunsigned, picks
SHRQ vs SARQ at emit
- chained-ptr-field compound RSHIFTEQ (cgen.c:3281-3317 area)
- N_INDEX-lhs compound RSHIFTEQ (#133-expanded N_INDEX site)
- deref-target compound RSHIFTEQ
- top-level let compound RSHIFTEQ
- IDENT-local compound RSHIFTEQ
All sites reuse the in-scope unsignd variable from the surrounding
SLASHEQ block (or derive one locally when not available). LSHIFTEQ
unchanged — SHL == SAL at the encoder, no signedness dispatch needed.
912_sar_shr_run: 5 rows. i32_neg_rshifteq (lead's repro, was wrong
1073741774 → now -50), i64_neg_rshifteq (wider type), i32_pos_
rshifteq (positive control, SARQ ≡ SHRQ on positives, no regression),
u32_rshifteq (unsigned control, still SHRQ), i32_neg_rshift_binop
(plain >> not compound, cgbin TK_RSHIFT site). Exit codes use small
absolute values with u8 wrap (-50 = 206) per Unix 8-bit exit.
Bootstrap-NEUTRAL — `grep -rE '>>=|>>\b'` in lib/+selfhost/ (excl.
combined.ww) returned zero callers of signed RSHIFT today; the only
asm shifts are on previously-broken paths. 990-997 + combined_ww_
fresh stay green. Closes the silent-misbehavior class on signed
right-shift across all 12 cgen emission paths in one fold per
rule-11. Foundation for Eisel-Lemire (strconv fold-4) big-int signed
shifts.
857 lines
27 KiB
Plaintext
857 lines
27 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 mem;
|
|
import opcodes;
|
|
|
|
// ---- 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 streq(a: str, b: str) bool = {
|
|
if (a.len != b.len) { return false; };
|
|
let i: i32 = 0;
|
|
for (i < a.len) {
|
|
if (a[i] != b[i]) { return false; };
|
|
i += 1;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
fn resolvelabel(a: *asm_, name: str) u64 = {
|
|
let s: *asym = a.syms;
|
|
for (s != nil) {
|
|
if (s.defined != 0) { if (streq(s.name, name)) { 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 (streq(s.name, name)) { 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;
|
|
if (op == A_JE) { cc = 132u8; } // 0x84
|
|
else { if (op == A_JZ) { cc = 132u8; }
|
|
else { if (op == A_JNE) { cc = 133u8; }
|
|
else { if (op == A_JNZ) { cc = 133u8; }
|
|
else { if (op == A_JL) { cc = 140u8; }
|
|
else { if (op == A_JLE) { cc = 142u8; }
|
|
else { if (op == A_JG) { cc = 143u8; }
|
|
else { if (op == A_JGE) { cc = 141u8; }
|
|
else { if (op == A_JB) { cc = 130u8; }
|
|
else { if (op == A_JBE) { cc = 134u8; }
|
|
else { if (op == A_JA) { cc = 135u8; }
|
|
else { if (op == A_JAE) { cc = 131u8; }
|
|
else { if (op == A_JP) { cc = 138u8; } // 0x8A, UCOMISD unordered (#97)
|
|
else { 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;
|
|
};
|