Files
ww/selfhost/cmd/w6a/opcodes.ww
Hojun-Cho fa136d0b88 cgen: f64 compare consults parity flag for NaN, 4 relops (both stages, #97)
UCOMISD/UCOMISS set PF=ZF=CF=1 on unordered (a NaN operand). The old
arms keyed on ZF/CF only, so 4 of the 6 relops mishandled NaN:
`nan != nan` was false (JNE keys on ZF=0), `nan == nan` was true, and
`<`/`<=` (JB/JBE) fired on the unordered CF=1. IEEE-754: any relop
with a NaN operand is unordered — `!=` true, the rest false. `!=` now
jumps to true on JNE OR JP; `==`/`<`/`<=` jump to false on JP before
the ordered Jcc.

`>`/`>=` (JA/JAE) are LEFT UNCHANGED: they require CF=0, which an
unordered UCOMISD never produces, so they already reject NaN
correctly. Adding a PF guard there would only churn their .s (an extra
JP on every >/>= float compare) for no correctness gain, so their arm
stays byte-identical to the pre-#97 single template.

Bundles the cgen fix with JP-mnemonic support in both assemblers
(w6c enum/printer + w6a/w6a_ww parse+encode, 0F 8A). They can't split:
the cgen emits JP, which has no encoding without the assembler change,
so a cgen-only commit would not build. JP is the only PF-sensitive
jump on amd64 — there is no alternative instruction.
2026-05-25 12:44:56 +09:00

222 lines
4.9 KiB
Plaintext

// selfhost/cmd/w6a/opcodes.ww — types + constants shared across the
// w6a port. Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
package w6a;
// ---- registers + operand kinds (from 6.out.h) -------------------------
// These must stay numerically aligned with the C enum so that ww-cgen
// output (which reads them via `D_AX(SB)` etc.) lands on the same
// integers when read by ww-w6a.
def D_NONE: i32 = 0;
def D_AX: i32 = 1;
def D_CX: i32 = 2;
def D_DX: i32 = 3;
def D_BX: i32 = 4;
def D_SP: i32 = 5;
def D_BP: i32 = 6;
def D_SI: i32 = 7;
def D_DI: i32 = 8;
def D_R8: i32 = 9;
def D_R9: i32 = 10;
def D_R10: i32 = 11;
def D_R11: i32 = 12;
def D_R12: i32 = 13;
def D_R13: i32 = 14;
def D_R14: i32 = 15;
def D_R15: i32 = 16;
def D_X0: i32 = 17;
def D_X1: i32 = 18;
def D_X2: i32 = 19;
def D_X3: i32 = 20;
def D_X4: i32 = 21;
def D_X5: i32 = 22;
def D_X6: i32 = 23;
def D_X7: i32 = 24;
def D_X8: i32 = 25;
def D_X9: i32 = 26;
def D_X10: i32 = 27;
def D_X11: i32 = 28;
def D_X12: i32 = 29;
def D_X13: i32 = 30;
def D_X14: i32 = 31;
def D_X15: i32 = 32;
def D_PSP: i32 = 33;
def D_PFP: i32 = 34;
def D_PSB: i32 = 35;
def D_CONST: i32 = 36;
def D_BRANCH: i32 = 37;
def D_EXTERN: i32 = 38;
def D_INDIR: i32 = 39;
// ---- opcodes ----------------------------------------------------------
def A_NOP: i32 = 0;
def A_TEXT: i32 = 1;
def A_DATA: i32 = 2;
def A_GLOBL: i32 = 3;
def A_END: i32 = 4;
def A_MOVQ: i32 = 5;
def A_MOVL: i32 = 6;
def A_MOVB: i32 = 7;
def A_MOVZBQ: i32 = 8;
def A_MOVSXD: i32 = 9;
def A_MOVW: i32 = 62;
def A_MOVZWQ: i32 = 63;
def A_MOVSWQ: i32 = 64;
def A_MOVSBQ: i32 = 65;
def A_MOVSD: i32 = 10;
def A_ADDSD: i32 = 11;
def A_SUBSD: i32 = 12;
def A_MULSD: i32 = 13;
def A_DIVSD: i32 = 14;
def A_UCOMISD: i32 = 15;
def A_CVTTSD2SI: i32 = 16;
def A_CVTSI2SD: i32 = 17;
def A_MOVSS: i32 = 18;
def A_ADDSS: i32 = 19;
def A_SUBSS: i32 = 20;
def A_MULSS: i32 = 21;
def A_DIVSS: i32 = 22;
def A_UCOMISS: i32 = 23;
def A_CVTTSS2SI: i32 = 24;
def A_CVTSI2SS: i32 = 25;
def A_CVTSD2SS: i32 = 26;
def A_CVTSS2SD: i32 = 27;
def A_ADDQ: i32 = 28;
def A_SUBQ: i32 = 29;
def A_IMULQ: i32 = 30;
def A_IDIVQ: i32 = 31;
def A_DIVQ: i32 = 32;
def A_NEGQ: i32 = 33;
def A_NOTQ: i32 = 34;
def A_ANDQ: i32 = 35;
def A_ORQ: i32 = 36;
def A_XORQ: i32 = 37;
def A_SHLQ: i32 = 38;
def A_SHRQ: i32 = 39;
def A_CMPQ: i32 = 40;
def A_PUSHQ: i32 = 41;
def A_POPQ: i32 = 42;
def A_LEAQ: i32 = 43;
def A_CALL: i32 = 44;
def A_RET: i32 = 45;
def A_JMP: i32 = 46;
def A_JE: i32 = 47;
def A_JNE: i32 = 48;
def A_JL: i32 = 49;
def A_JLE: i32 = 50;
def A_JG: i32 = 51;
def A_JGE: i32 = 52;
def A_JB: i32 = 53;
def A_JBE: i32 = 54;
def A_JA: i32 = 55;
def A_JAE: i32 = 56;
def A_JZ: i32 = 57;
def A_JNZ: i32 = 58;
// 67 (next free above A_CQO=66): appended so the existing A_MOV*/
// A_SYSCALL/A_DATAW/A_DATAR/A_CQO numbers stay put. Jump on
// parity (PF=1): UCOMISD unordered (#97).
def A_JP: i32 = 67;
def A_SYSCALL: i32 = 59;
// Writable data + reloc-only data. Mirror cmd/w6c/6.out.h.
// A_DATAW: bytes land in .data (RW) instead of .text.
// A_DATAR: record an R_X86_64_64 reloc at a .data slot, patched
// to a target symbol's runtime VA at link time.
def A_DATAW: i32 = 60;
def A_DATAR: i32 = 61;
// REX.W 99 — sign-extend RAX into RDX:RAX. Pairs with IDIVQ for
// signed division; pendant to the MOVQ $0, DX zero-fill that pairs
// with DIVQ.
def A_CQO: i32 = 66;
// ---- structs (mirror cmd/w6a/a.h) --------------------------------------
type aoperand = struct {
atype: i32, // D_NONE / D_AX..D_R15 / D_CONST / D_INDIR / D_EXTERN / D_BRANCH
reg: i32,
offset: i64,
asym: str,
};
// `from` and `to` are pointer-to-aoperand (rather than embedded).
// The C cgen doesn't support chained-dot through embedded value
// fields, so allocating each operand once per prog lets us write
// `p.to.atype` directly.
type aprog = struct {
as_: i32,
from: *aoperand,
to: *aoperand,
line: i32,
label: str,
link: *aprog,
bytes: *u8, // payload for A_DATA
nbytes: u64,
};
type asym = struct {
name: str,
defined: i32,
istext: i32,
isdata: i32, // mutually exclusive with istext; DATAW symbols
isglobal: i32,
addr: u64, // offset within its section (.text or .data)
idx: i32,
snext: *asym,
};
type areloc = struct {
off: u64,
section: i32, // 0 = .text, 1 = .data
kind: i32,
asy: *asym,
addend: i64,
rnext: *areloc,
};
type afixup = struct {
off: u64, // where the rel32 lands in .text
label: str,
fnext: *afixup,
};
type asm_ = struct {
file: str,
src: *u8,
srclen: u64,
pos: u64,
line: i32,
head: *aprog,
tail: *aprog,
text: *u8,
textcap: u64,
textlen: u64,
// Writable .data. Empty unless any DATAW directive was seen;
// obj.ww emits the extra section conditionally so .o output
// stays byte-identical for inputs that don't use DATAW (test
// 991 byte-diff invariant).
data: *u8,
datacap: u64,
datalen: u64,
syms: *asym,
relocs: *areloc,
fixups: *afixup,
errs: i32,
};