fix(disasm): name the amd64 families x/arch decodes to the zero opcode

x/arch reports the ADCX, ADOX, RDSEED, RDPID, TPAUSE, UMONITOR, UMWAIT
and ENDBR families with no error but the degenerate zero instruction,
which GoSyntax renders as Op(0) under its prefix decoration and with a
length of one.  A supplementary naming table keyed by the opcode
pattern restores the toolchain's own spellings and lengths; the parity
fixtures pin all 41 corpus rows (ENDBR32 alone, which the toolchain
cannot spell, pins as bytes and text in the focused naming test).

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 02:27:51 +02:00
1 parent 6c4932c4ec
commit dd356b9e6a
4 files changed
+498

No files matched your search

+128
View File
@@ -0,0 +1,128 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package disasm
import (
"testing"
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
)
// TestDegenerateNaming pins the supplementary naming table over the whole
// set of encodings the Go toolchain's assembler corpus carries for the
// families x/arch decodes to the degenerate zero instruction. Every row's
// bytes are the corpus's own expected-encoding comments (machine-checked
// by the toolchain's assembler test suite); every text is the corpus's own
// spelling of the instruction.
//
// The rows whose text the encoder carries are also in
// testdata/parity_amd64_unlisted.txt, where the parity and round-trip
// tests pin them; this table covers them too so a fixture edit cannot
// move one without this one noticing. ENDBR32 is the exception: the
// toolchain knows no spelling for it (its obj/x86 table carries ENDBR64
// only), so its text is pinned here as bytes and text, with no
// round-trip.
func TestDegenerateNaming(t *testing.T) {
for _, tt := range []struct {
code []byte
text string
}{
// amd64enc.s: ADCXL (BX), DX // 660f38f613 and kin.
{[]byte{0x66, 0x0f, 0x38, 0xf6, 0x13}, "ADCXL 0(BX), DX"},
{[]byte{0x66, 0x41, 0x0f, 0x38, 0xf6, 0x13}, "ADCXL 0(R11), DX"},
{[]byte{0x66, 0x0f, 0x38, 0xf6, 0xd2}, "ADCXL DX, DX"},
{[]byte{0x66, 0x41, 0x0f, 0x38, 0xf6, 0xd3}, "ADCXL R11, DX"},
{[]byte{0x66, 0x44, 0x0f, 0x38, 0xf6, 0x1b}, "ADCXL 0(BX), R11"},
{[]byte{0x66, 0x45, 0x0f, 0x38, 0xf6, 0x1b}, "ADCXL 0(R11), R11"},
{[]byte{0x66, 0x44, 0x0f, 0x38, 0xf6, 0xda}, "ADCXL DX, R11"},
{[]byte{0x66, 0x45, 0x0f, 0x38, 0xf6, 0xdb}, "ADCXL R11, R11"},
{[]byte{0x66, 0x48, 0x0f, 0x38, 0xf6, 0x13}, "ADCXQ 0(BX), DX"},
{[]byte{0x66, 0x49, 0x0f, 0x38, 0xf6, 0x13}, "ADCXQ 0(R11), DX"},
{[]byte{0x66, 0x48, 0x0f, 0x38, 0xf6, 0xd2}, "ADCXQ DX, DX"},
{[]byte{0x66, 0x49, 0x0f, 0x38, 0xf6, 0xd3}, "ADCXQ R11, DX"},
{[]byte{0x66, 0x4c, 0x0f, 0x38, 0xf6, 0x1b}, "ADCXQ 0(BX), R11"},
{[]byte{0x66, 0x4d, 0x0f, 0x38, 0xf6, 0x1b}, "ADCXQ 0(R11), R11"},
{[]byte{0x66, 0x4c, 0x0f, 0x38, 0xf6, 0xda}, "ADCXQ DX, R11"},
{[]byte{0x66, 0x4d, 0x0f, 0x38, 0xf6, 0xdb}, "ADCXQ R11, R11"},
// amd64enc.s: ADOXL (BX), DX // f30f38f613 and kin.
{[]byte{0xf3, 0x0f, 0x38, 0xf6, 0x13}, "ADOXL 0(BX), DX"},
{[]byte{0xf3, 0x41, 0x0f, 0x38, 0xf6, 0x13}, "ADOXL 0(R11), DX"},
{[]byte{0xf3, 0x0f, 0x38, 0xf6, 0xd2}, "ADOXL DX, DX"},
{[]byte{0xf3, 0x41, 0x0f, 0x38, 0xf6, 0xd3}, "ADOXL R11, DX"},
{[]byte{0xf3, 0x44, 0x0f, 0x38, 0xf6, 0x1b}, "ADOXL 0(BX), R11"},
{[]byte{0xf3, 0x45, 0x0f, 0x38, 0xf6, 0x1b}, "ADOXL 0(R11), R11"},
{[]byte{0xf3, 0x44, 0x0f, 0x38, 0xf6, 0xda}, "ADOXL DX, R11"},
{[]byte{0xf3, 0x45, 0x0f, 0x38, 0xf6, 0xdb}, "ADOXL R11, R11"},
{[]byte{0xf3, 0x48, 0x0f, 0x38, 0xf6, 0x13}, "ADOXQ 0(BX), DX"},
{[]byte{0xf3, 0x49, 0x0f, 0x38, 0xf6, 0x13}, "ADOXQ 0(R11), DX"},
{[]byte{0xf3, 0x48, 0x0f, 0x38, 0xf6, 0xd2}, "ADOXQ DX, DX"},
{[]byte{0xf3, 0x49, 0x0f, 0x38, 0xf6, 0xd3}, "ADOXQ R11, DX"},
{[]byte{0xf3, 0x4c, 0x0f, 0x38, 0xf6, 0x1b}, "ADOXQ 0(BX), R11"},
{[]byte{0xf3, 0x4d, 0x0f, 0x38, 0xf6, 0x1b}, "ADOXQ 0(R11), R11"},
{[]byte{0xf3, 0x4c, 0x0f, 0x38, 0xf6, 0xda}, "ADOXQ DX, R11"},
{[]byte{0xf3, 0x4d, 0x0f, 0x38, 0xf6, 0xdb}, "ADOXQ R11, R11"},
// amd64enc.s: RDSEEDW DX // 660fc7fa.
{[]byte{0x66, 0x0f, 0xc7, 0xfa}, "RDSEEDW DX"},
{[]byte{0x66, 0x41, 0x0f, 0xc7, 0xfb}, "RDSEEDW R11"},
// amd64enc_extra.s: RDPID DX // f30fc7fa.
{[]byte{0xf3, 0x0f, 0xc7, 0xfa}, "RDPID DX"},
{[]byte{0xf3, 0x41, 0x0f, 0xc7, 0xfb}, "RDPID R11"},
// amd64enc_extra.s: TPAUSE BX // 660faef3 and kin.
{[]byte{0x66, 0x0f, 0xae, 0xf3}, "TPAUSE BX"},
{[]byte{0xf3, 0x0f, 0xae, 0xf3}, "UMONITOR BX"},
{[]byte{0xf2, 0x0f, 0xae, 0xf3}, "UMWAIT BX"},
// amd64enc_extra.s: ENDBR64 // f30f1efa; ENDBR32 has no
// toolchain spelling.
{[]byte{0xf3, 0x0f, 0x1e, 0xfa}, "ENDBR64"},
{[]byte{0xf3, 0x0f, 0x1e, 0xfb}, "ENDBR32"},
} {
ins, err := Decode(arch.AMD64, tt.code, 0)
if err != nil {
t.Errorf("% x: %v", tt.code, err)
continue
}
if ins.Text != tt.text || ins.Len != len(tt.code) {
t.Errorf("% x: %q (%d bytes), want %q (%d)",
tt.code, ins.Text, ins.Len, tt.text, len(tt.code))
}
}
}
// TestDegenerateNamingBoundaries guards the table's edges: bytes the
// decoder rejects outright keep the placeholder, and the prefix
// combinations no toolchain spelling carries stay unnamed even where the
// decode is degenerate. The bare 0F 38 F6 form is the 32-bit ADCX of the
// Intel manual; the toolchain's ADCXL spelling always carries the
// operand-size override, so the bare form is left to the placeholder
// rather than named to a spelling that would re-encode differently.
func TestDegenerateNamingBoundaries(t *testing.T) {
for _, tt := range []struct {
code []byte
text string
}{
// Rejected outright: RDSEED without the operand-size override
// (the bare 0F C7 /7 register form), MONITORX and MWAITX, the
// hint NOP CLDEMOTE.
{[]byte{0x0f, 0xc7, 0xfa}, "???"},
{[]byte{0x0f, 0x01, 0xfa}, "???"},
{[]byte{0x0f, 0x01, 0xfb}, "???"},
{[]byte{0x0f, 0x1c, 0x03}, "???"},
// Degenerate but outside the table's prefix gates: repne ADCX is
// no instruction the corpus names.
{[]byte{0xf2, 0x0f, 0x38, 0xf6, 0xd2}, "REPNE; Op(0)"},
// MONITOR and MWAIT decode as named opcodes and never reach the
// table.
{[]byte{0x0f, 0x01, 0xc8}, "MONITOR"},
{[]byte{0x0f, 0x01, 0xc9}, "MWAIT"},
} {
ins, err := Decode(arch.AMD64, tt.code, 0)
if err != nil {
t.Errorf("% x: %v", tt.code, err)
continue
}
if ins.Text != tt.text {
t.Errorf("% x: %q, want %q", tt.code, ins.Text, tt.text)
}
}
}