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

+18 -1
View File
@@ -257,6 +257,19 @@ func (e *enc) encode(mnem string, ops []Operand) error {
}
base, size := splitSize(upper)
// The scalar families that own byte forms settle the width against the
// register operands here, before the unsuffixed 64-bit default applies,
// so a literal Q suffix stays distinguishable from no suffix at all
// (CRC32 DL, R11 is the byte form; CRC32Q DL, R11 is a conflict).
if byteFormBase[base] {
w, err := operandWidth(mnem, size, widthOperands(base, ops))
if err != nil {
return err
}
if w != 0 {
size = w
}
}
if size == 0 {
size = 8 // default operand size in 64-bit mode (e.g. PUSHQ)
}
@@ -306,8 +319,12 @@ func (e *enc) encode(mnem string, ops []Operand) error {
case "MOV":
return e.encodeMov(ops, size)
// MOVD is the Go assembler's alias of MOVQ: the same byte forms, 64-bit
// REX.W and all.
// REX.W and all. The alias takes no byte register either, the same
// conflict rule the Q-suffixed spelling answers to.
case "MOVD":
if _, err := operandWidth(mnem, 8, ops); err != nil {
return err
}
return e.encodeMov(ops, 8)
case "ADD", "SUB", "AND", "OR", "XOR", "CMP", "ADC", "SBB":
return e.encodeALU(aluOp[base], ops, size)