The names naming_amd64.go and naming_amd64_test.go carried the _amd64 filename suffix, which the go tool reads as an implicit GOARCH=amd64 build constraint: the tables disappeared from every non-amd64 build and the package failed to compile for arm64, riscv64 and loong64, the other three architectures the tool assembles. Renaming to amd64_naming.go removes the constraint; the module builds again for all four GOARCH values. Assisted-by: GLM 5.3
187 lines
8.8 KiB
Go
187 lines
8.8 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)"},
|
|
// amd64enc.s: the BMI1/BMI2 VEX families the decoder refuses
|
|
// with "unknown AVX Opcode". One row per family and operand
|
|
// shape; every corpus row is pinned in the unlisted fixture.
|
|
{[]byte{0xc4, 0xe2, 0x30, 0xf2, 0x13}, "ANDNL 0(BX), R9, DX"},
|
|
{[]byte{0xc4, 0xe2, 0x88, 0xf2, 0xd2}, "ANDNQ DX, R14, DX"},
|
|
{[]byte{0xc4, 0xe2, 0x30, 0xf7, 0x13}, "BEXTRL R9, 0(BX), DX"},
|
|
{[]byte{0xc4, 0x62, 0x88, 0xf7, 0xda}, "BEXTRQ R14, DX, R11"},
|
|
{[]byte{0xc4, 0xe2, 0x30, 0xf3, 0x1b}, "BLSIL 0(BX), R9"},
|
|
{[]byte{0xc4, 0xe2, 0x30, 0xf3, 0x13}, "BLSMSKL 0(BX), R9"},
|
|
{[]byte{0xc4, 0xe2, 0x30, 0xf3, 0x0b}, "BLSRL 0(BX), R9"},
|
|
{[]byte{0xc4, 0xe2, 0x88, 0xf3, 0xca}, "BLSRQ DX, R14"},
|
|
{[]byte{0xc4, 0xe2, 0x30, 0xf5, 0x13}, "BZHIL R9, 0(BX), DX"},
|
|
{[]byte{0xc4, 0x42, 0x88, 0xf5, 0xdb}, "BZHIQ R14, R11, R11"},
|
|
{[]byte{0xc4, 0xe2, 0x33, 0xf6, 0x13}, "MULXL 0(BX), R9, DX"},
|
|
{[]byte{0xc4, 0x62, 0x8b, 0xf6, 0xda}, "MULXQ DX, R14, R11"},
|
|
{[]byte{0xc4, 0xe2, 0x33, 0xf5, 0x13}, "PDEPL 0(BX), R9, DX"},
|
|
{[]byte{0xc4, 0xe2, 0x32, 0xf5, 0x13}, "PEXTL 0(BX), R9, DX"},
|
|
{[]byte{0xc4, 0xe3, 0x7b, 0xf0, 0x13, 0x07}, "RORXL $7, 0(BX), DX"},
|
|
{[]byte{0xc4, 0xe3, 0xfb, 0xf0, 0x10, 0xff}, "RORXQ $-1, 0(AX), DX"},
|
|
{[]byte{0xc4, 0xe2, 0x32, 0xf7, 0x13}, "SARXL R9, 0(BX), DX"},
|
|
{[]byte{0xc4, 0xe2, 0x31, 0xf7, 0x13}, "SHLXL R9, 0(BX), DX"},
|
|
{[]byte{0xc4, 0xe2, 0x33, 0xf7, 0x13}, "SHRXL R9, 0(BX), DX"},
|
|
{[]byte{0xc4, 0x42, 0x89, 0xf7, 0xdb}, "SHLXQ R14, R11, R11"},
|
|
// amd64enc.s: CLAC // 0f01ca, STAC // 0f01cb, RDPKRU // 0f01ee
|
|
// and WRPKRU // 0f01ef; the decoder rejects the encodings.
|
|
{[]byte{0x0f, 0x01, 0xca}, "CLAC"},
|
|
{[]byte{0x0f, 0x01, 0xcb}, "STAC"},
|
|
{[]byte{0x0f, 0x01, 0xee}, "RDPKRU"},
|
|
{[]byte{0x0f, 0x01, 0xef}, "WRPKRU"},
|
|
// amd64enc.s: RDSEEDL DX // 0fc7fa and RDSEEDQ DX // 480fc7fa.
|
|
// The bare and REX-only forms are refused outright (the 66 and
|
|
// f3 forms above come back degenerate), so they are named in
|
|
// the rejected-encoding table.
|
|
{[]byte{0x0f, 0xc7, 0xfa}, "RDSEEDL DX"},
|
|
{[]byte{0x41, 0x0f, 0xc7, 0xfb}, "RDSEEDL R11"},
|
|
{[]byte{0x48, 0x0f, 0xc7, 0xfa}, "RDSEEDQ DX"},
|
|
{[]byte{0x49, 0x0f, 0xc7, 0xfb}, "RDSEEDQ R11"},
|
|
// amd64enc.s: UD1 // 0fb9, decoded with no error but the
|
|
// degenerate zero instruction.
|
|
{[]byte{0x0f, 0xb9}, "UD1"},
|
|
} {
|
|
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: MONITORX and MWAITX, and the register
|
|
// form of the hint NOP opcode, which the corpus does not
|
|
// spell.
|
|
{[]byte{0x0f, 0x01, 0xfa}, "???"},
|
|
{[]byte{0x0f, 0x01, 0xfb}, "???"},
|
|
{[]byte{0x0f, 0x1c, 0xc3}, "???"},
|
|
// The 0F 01 family keeps its fixed three bytes: a REX prefix
|
|
// extends nothing the instructions carry.
|
|
{[]byte{0x41, 0x0f, 0x01, 0xca}, "???"},
|
|
// The VEX families stay pinned to the corpus prefix shapes:
|
|
// the vector-length bit set (a reserved encoding in every GPR
|
|
// family), the operand-size selector on the ANDN opcode (the
|
|
// selector belongs to no F2 family), RORX with a live vvvv
|
|
// field (the toolchain keeps it dead), the BLS* selector
|
|
// outside its three defined reg fields, and the one-byte-map
|
|
// escape, which no family here uses.
|
|
{[]byte{0xc4, 0xe3, 0x34, 0xf2, 0x13}, "???"},
|
|
{[]byte{0xc4, 0xe2, 0x31, 0xf2, 0x13}, "???"},
|
|
{[]byte{0xc4, 0xe3, 0x63, 0xf0, 0x13, 0x07}, "???"},
|
|
{[]byte{0xc4, 0xe2, 0x30, 0xf3, 0x03}, "???"},
|
|
{[]byte{0xc4, 0xe1, 0x70, 0xf2, 0x13}, "???"},
|
|
// 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)
|
|
}
|
|
}
|
|
}
|