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.
226 lines
5.1 KiB
Plaintext
226 lines
5.1 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;
|
|
// #136: arithmetic right-shift, sign-extends MSB. SHR injects
|
|
// zeros and is wrong for signed operands; cgen routes signed
|
|
// `>>` / `>>=` through SAR after this opcode landed.
|
|
def A_SARQ: i32 = 68;
|
|
|
|
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,
|
|
};
|