Files
gasm-sdk/disasm/naming_amd64_test.go
T
petrbalvin 458d981f31 feat(disasm): name the CLDEMOTE encoding the decoder refuses
The hint NOP opcode 0F 1C /r with a memory operand is CLDEMOTE, a
memory-only instruction the toolchain's own table carries; the decoder
rejects the encoding instead of naming it.  The rejected-encoding side
of the supplementary table names it from the bytes, and the corpus row
0f1c03 pins the text in the unlisted fixture, round trip byte exact.

Assisted-by: GLM 5.3 Flash
2026-10-07 02:27:51 +02:00

134 lines
5.9 KiB
Go

// 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"},
// amd64enc_extra.s: CLDEMOTE (BX) // 0f1c03. The decoder
// rejects the encoding outright; the table names it from the
// bytes.
{[]byte{0x0f, 0x1c, 0x03}, "CLDEMOTE 0(BX)"},
} {
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, and
// the register form of the hint NOP opcode, which the corpus
// does not spell.
{[]byte{0x0f, 0xc7, 0xfa}, "???"},
{[]byte{0x0f, 0x01, 0xfa}, "???"},
{[]byte{0x0f, 0x01, 0xfb}, "???"},
{[]byte{0x0f, 0x1c, 0xc3}, "???"},
// 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)
}
}
}