From 06c40a8befcae7ef8b5fee387e5cb108920c401d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 9 Aug 2026 01:07:06 +0900 Subject: [PATCH] w6a: MOVB REX for SI/DI/BP/SP; LEAQ/CALL shapes err; honest diag lengths 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. --- cmd/w6a/asm.c | 30 +++++++++++++++--- selfhost/cmd/w6a/asm.ww | 68 ++++++++++++++++++++++++++++++---------- selfhost/cmd/w6a/main.ww | 21 ++++++++----- 3 files changed, 91 insertions(+), 28 deletions(-) diff --git a/cmd/w6a/asm.c b/cmd/w6a/asm.c index 81a59a3d..0a898c2e 100644 --- a/cmd/w6a/asm.c +++ b/cmd/w6a/asm.c @@ -147,6 +147,19 @@ emit_rex(Asm *a, int regbit, int rmbit, int w) 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) @@ -521,17 +534,20 @@ a_encode(Asm *a) } 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. */ + /* 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_rex(a, rhi(p->from.type), rhi(p->to.reg), 0); + 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_rex(a, rhi(p->to.type), rhi(p->from.reg), 0); + 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); @@ -772,6 +788,9 @@ a_encode(Asm *a) 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: @@ -790,6 +809,9 @@ a_encode(Asm *a) 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: diff --git a/selfhost/cmd/w6a/asm.ww b/selfhost/cmd/w6a/asm.ww index 6f2560b3..3172ceb9 100644 --- a/selfhost/cmd/w6a/asm.ww +++ b/selfhost/cmd/w6a/asm.ww @@ -105,6 +105,17 @@ fn emitrex(a: *asm_, regbit: i32, rmbit: i32, w: i32) void = { else { if (w != 0) { emitbyte(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. +fn emitrex8(a: *asm_, regbit: i32, rmbit: i32, r8: i32) void = { + let b: u8 = 64u8; // 0x40 + if (regbit != 0) { b = b | 4u8; }; + if (rmbit != 0) { b = b | 1u8; }; + if (b != 64u8) { emitbyte(a, b); } + else { if (rhi(r8) == 0 && rcode(r8) >= 4) { 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 = { @@ -283,7 +294,8 @@ export fn encode(a: *asm_) i32 = { let msg: str = "w6a: DATAR slot not yet defined as DATAW: "; os.write(2, msg.ptr, msg.len: u64); os.write(2, p.from.asym.ptr, p.from.asym.len: u64); - os.write(2, "\n".ptr, 1u64); + let wm1: str = "\n"; + os.write(2, wm1.ptr, wm1.len: u64); a.errs += 1; p = p.link; continue; }; @@ -382,27 +394,31 @@ export fn encode(a: *asm_) i32 = { addreloc(a, reloff, 2, s, -4i64); p = p.link; continue; };}; - os.write(2, "w6a: unsupported MOVQ shape\n".ptr, 27u64); + let wm2: str = "w6a: unsupported MOVQ shape\n"; + os.write(2, wm2.ptr, wm2.len: u64); a.errs += 1; p = p.link; continue; }; if (op == A_MOVB) { + // emitrex8 forces the bare REX so SI/DI/BP/SP name + // SIL/DIL/BPL/SPL, never AH/CH/DH/BH. 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); + emitrex8(a, rhi(ft), rhi(p.to.reg), ft); 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); + emitrex8(a, rhi(tt), rhi(p.from.reg), tt); 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); + let wm3: str = "w6a: unsupported MOVB shape\n"; + os.write(2, wm3.ptr, wm3.len: u64); a.errs += 1; p = p.link; continue; }; @@ -426,7 +442,8 @@ export fn encode(a: *asm_) i32 = { emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset); p = p.link; continue; };}; - os.write(2, "w6a: unsupported MOVW shape\n".ptr, 27u64); + let wm4: str = "w6a: unsupported MOVW shape\n"; + os.write(2, wm4.ptr, wm4.len: u64); a.errs += 1; p = p.link; continue; }; @@ -442,7 +459,8 @@ export fn encode(a: *asm_) i32 = { emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset); p = p.link; continue; };}; - os.write(2, "w6a: unsupported MOVZWQ shape\n".ptr, 29u64); + let wm5: str = "w6a: unsupported MOVZWQ shape\n"; + os.write(2, wm5.ptr, wm5.len: u64); a.errs += 1; p = p.link; continue; }; @@ -465,7 +483,8 @@ export fn encode(a: *asm_) i32 = { emitbyte(a, modrmbyte(3, rcode(tt), rcode(ft))); p = p.link; continue; };}; - os.write(2, "w6a: unsupported MOVSWQ shape\n".ptr, 29u64); + let wm6: str = "w6a: unsupported MOVSWQ shape\n"; + os.write(2, wm6.ptr, wm6.len: u64); a.errs += 1; p = p.link; continue; }; @@ -488,7 +507,8 @@ export fn encode(a: *asm_) i32 = { emitbyte(a, modrmbyte(3, rcode(tt), rcode(ft))); p = p.link; continue; };}; - os.write(2, "w6a: unsupported MOVSBQ shape\n".ptr, 29u64); + let wm7: str = "w6a: unsupported MOVSBQ shape\n"; + os.write(2, wm7.ptr, wm7.len: u64); a.errs += 1; p = p.link; continue; }; @@ -503,7 +523,8 @@ export fn encode(a: *asm_) i32 = { emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset); p = p.link; continue; };}; - os.write(2, "w6a: unsupported MOVZBQ shape\n".ptr, 29u64); + let wm8: str = "w6a: unsupported MOVZBQ shape\n"; + os.write(2, wm8.ptr, wm8.len: u64); a.errs += 1; p = p.link; continue; }; @@ -529,7 +550,8 @@ export fn encode(a: *asm_) i32 = { emitbyte(a, modrmbyte(3, rcode(ft), rcode(tt))); p = p.link; continue; };}; - os.write(2, "w6a: unsupported MOVL shape\n".ptr, 27u64); + let wm9: str = "w6a: unsupported MOVL shape\n"; + os.write(2, wm9.ptr, wm9.len: u64); a.errs += 1; p = p.link; continue; }; @@ -549,7 +571,8 @@ export fn encode(a: *asm_) i32 = { emitbyte(a, modrmbyte(3, rcode(tt), rcode(ft))); p = p.link; continue; };}; - os.write(2, "w6a: unsupported MOVSXD shape\n".ptr, 29u64); + let wm10: str = "w6a: unsupported MOVSXD shape\n"; + os.write(2, wm10.ptr, wm10.len: u64); a.errs += 1; p = p.link; continue; }; @@ -569,7 +592,8 @@ export fn encode(a: *asm_) i32 = { 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); + let wm11: str = "w6a: unsupported MOVSD shape\n"; + os.write(2, wm11.ptr, wm11.len: u64); a.errs += 1; p = p.link; continue; }; @@ -596,7 +620,8 @@ export fn encode(a: *asm_) i32 = { 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); + let wm12: str = "w6a: unsupported MOVSS shape\n"; + os.write(2, wm12.ptr, wm12.len: u64); a.errs += 1; p = p.link; continue; }; @@ -737,6 +762,9 @@ export fn encode(a: *asm_) i32 = { addreloc(a, reloff, 2, s, -4i64); p = p.link; continue; };}; + let wml: str = "w6a: unsupported LEAQ shape\n"; + os.write(2, wml.ptr, wml.len: u64); + a.errs += 1; p = p.link; continue; }; @@ -762,6 +790,9 @@ export fn encode(a: *asm_) i32 = { emitbyte(a, modrmbyte(3, 2, rcode(tt))); p = p.link; continue; }; + let wmc: str = "w6a: unsupported CALL shape\n"; + os.write(2, wmc.ptr, wmc.len: u64); + a.errs += 1; p = p.link; continue; }; @@ -799,7 +830,8 @@ export fn encode(a: *asm_) i32 = { p = p.link; continue; }; - os.write(2, "w6a: unsupported opcode\n".ptr, 23u64); + let wm13: str = "w6a: unsupported opcode\n"; + os.write(2, wm13.ptr, wm13.len: u64); a.errs += 1; p = p.link; }; @@ -808,10 +840,12 @@ export fn encode(a: *asm_) i32 = { let f: *afixup = a.fixups; for (f != nil) { if (!labeldefined(a, f.label)) { - os.write(2, "w6a: undefined label '".ptr, 21u64); + let wm14: str = "w6a: undefined label '"; + os.write(2, wm14.ptr, wm14.len: u64); let lbl: str = f.label; os.write(2, lbl.ptr, lbl.len: u64); - os.write(2, "'\n".ptr, 2u64); + let wm15: str = "'\n"; + os.write(2, wm15.ptr, wm15.len: u64); a.errs += 1; f = f.fnext; continue; diff --git a/selfhost/cmd/w6a/main.ww b/selfhost/cmd/w6a/main.ww index 2b879661..6036244a 100644 --- a/selfhost/cmd/w6a/main.ww +++ b/selfhost/cmd/w6a/main.ww @@ -73,16 +73,19 @@ export fn main(argc: i32, argv: **u8) i32 = { if (cstreq(a, "-o")) { i += 1; if (i >= argc) { - os.write(2, "w6a: -o requires arg\n".ptr, 20u64); + let wm1: str = "w6a: -o requires arg\n"; + os.write(2, wm1.ptr, wm1.len: u64); return 2; }; out = argv[i]; } else { if (a[0u64] == 45u8) { - os.write(2, "w6a: unknown flag\n".ptr, 17u64); + let wm2: str = "w6a: unknown flag\n"; + os.write(2, wm2.ptr, wm2.len: u64); return 2; } else { if (src != nil) { - os.write(2, "w6a: only one input\n".ptr, 19u64); + let wm3: str = "w6a: only one input\n"; + os.write(2, wm3.ptr, wm3.len: u64); return 2; }; src = a; @@ -91,11 +94,13 @@ export fn main(argc: i32, argv: **u8) i32 = { }; if (src == nil) { - os.write(2, "usage: w6a_ww -o file.o file.s\n".ptr, 30u64); + let wm4: str = "usage: w6a_ww -o file.o file.s\n"; + os.write(2, wm4.ptr, wm4.len: u64); return 2; }; if (out == nil) { - os.write(2, "w6a: missing -o\n".ptr, 15u64); + let wm5: str = "w6a: missing -o\n"; + os.write(2, wm5.ptr, wm5.len: u64); return 2; }; @@ -103,7 +108,8 @@ export fn main(argc: i32, argv: **u8) i32 = { let blen: u64; buf, blen = slurp(src); if (buf == nil) { - os.write(2, "w6a: cannot read input\n".ptr, 22u64); + let wm6: str = "w6a: cannot read input\n"; + os.write(2, wm6.ptr, wm6.len: u64); return 1; }; @@ -120,7 +126,8 @@ export fn main(argc: i32, argv: **u8) i32 = { let fd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 if (fd < 0) { - os.write(2, "w6a: cannot open output\n".ptr, 23u64); + let wm7: str = "w6a: cannot open output\n"; + os.write(2, wm7.ptr, wm7.len: u64); return 1; }; let rc: i32 = emitelf(&s, fd);