fix(asm): settle the byte-form width from the register operands

The suffixed scalar families derived the operand width from the mnemonic
alone, so a byte-spelled register under the L spelling or no suffix at all
encoded the widened form: XADDL DL, DL emitted 0F C1 where the byte form is
0F C0, CMPL AL, $7 emitted the 32-bit immediate form where the AL form is
3C 07, and CRC32 DL, R11 widened past the F0 byte opcode.  operandWidth now
reconciles the suffix with the operands: a byte register (AL, DL, R8B, ...)
forces the 8-bit form, which is the text the toolchain's own disassembly
prints for those encodings, while the W and Q spellings never ride a byte
register and are refused as go tool asm refuses them (MOVQ AL, AX).  The
shift count and the two- and three-operand IMUL forms stay out of the
reconciliation, and the byte accumulator short forms now belong to the AL
spelling alone, matching the toolchain's division (ADDB $3, AX is
80 c0 03, TESTB $7, AX is f6 c0 07).

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:49:58 +02:00
1 parent 33e7fdac98
commit 98a562d8b3
3 files changed
+110 -5

No files matched your search

+9 -4
View File
@@ -529,8 +529,10 @@ func (e *enc) encodeALUImm(digit int, dst Operand, imm int64, size int) error {
return err
}
// The byte accumulator short form (0x04+digit*8, no ModR/M) when
// the destination is AL, the form the Go assembler prefers here.
if r, ok := dst.(Reg); ok && r.idx == 0 {
// the destination is spelled AL itself; the size-agnostic AX takes
// the generic 0x80 /digit form, the division go tool asm makes
// (ADDB $7, AL is 04 07 while ADDB $3, AX is 80 c0 03).
if r, ok := dst.(Reg); ok && r.idx == 0 && byteReg(r) {
i := &instr{opcode: []byte{byte(0x04 + digit*8)}, modrm: -1, sib: -1}
i.imm = immBytes
return e.emit(i)
@@ -587,8 +589,11 @@ func (e *enc) encodeTest(ops []Operand, size int) error {
if imm, ok := src.(Imm); ok {
// TEST r/m, imm: 0xF6 (8-bit) / 0xF7 /0, but the Go assembler
// always uses the accumulator forms (A8/A9, no ModR/M) when the
// register operand is AL/AX, whatever the immediate's width.
if r, ok := dst.(Reg); ok && r.idx == 0 {
// register operand is AL/AX, whatever the immediate's width. At
// byte width the short form belongs to the AL spelling alone; the
// size-agnostic AX takes the generic F6 /0 (TESTB $7, AX is
// f6 c0 07), the same division the ALU accumulator makes.
if r, ok := dst.(Reg); ok && r.idx == 0 && (size != 1 || byteReg(r)) {
op := byte(0xA9)
if size == 1 {
op = 0xA8