feat(disasm): render the loong64 families x/arch names Unknown

x/arch's Plan 9 renderer decodes thirty-odd scalar loong64 operations
perfectly but prints them as "Unknown OP args", and names the
sign-extension pair EXT.W.B/EXT.W.H "?".  The supplementary naming
pass re-renders them with the toolchain's own spellings: the families
whose operand order x/arch already prints the Plan 9 way trade only
the mnemonic, and the pointer loads and stores, the acquire loads,
the release stores, PRELD and ALSL are rebuilt from the decoded
arguments with the toolchain's operand order and its raw displacement
reading.  ADDU16I.D, a macro helper the assembler never takes as
input, keeps the decoder's Unknown render.

The loong64 parity fixture grows from 73 to 385 rows, pinning every
unique four-byte corpus word the decoder accepts, and the LL/SC
displacement divergence in the encoder is documented for asm.

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:50:54 +02:00
1 parent 0f6e008c9a
commit a7f9d5eb67
5 files changed
+579 -11

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
// 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"
)
// TestUnknownNaming pins the loong64 re-rendering table over the corpus
// rows whose operations x/arch decodes but names "Unknown" or "?": every
// row's bytes are the corpus's own expected-encoding comments
// (machine-checked by the toolchain's assembler test suite) and every text
// is the toolchain's own spelling, operand order included.
func TestUnknownNaming(t *testing.T) {
for _, tt := range []struct {
code []byte
text string
}{
// loong64enc1.s: EXTWB R4, R5 // 855c0000 and EXTWH R4, R5 // 85580000.
{[]byte{0x85, 0x5c, 0x00, 0x00}, "EXTWB R4, R5"},
{[]byte{0x85, 0x58, 0x00, 0x00}, "EXTWH R4, R5"},
// loong64enc1.s: the bit and reverse families.
{[]byte{0x85, 0x40, 0x00, 0x00}, "REVH2W R4, R5"},
{[]byte{0x85, 0x44, 0x00, 0x00}, "REVHV R4, R5"},
{[]byte{0x85, 0x48, 0x00, 0x00}, "BITREV4B R4, R5"},
{[]byte{0x85, 0x4c, 0x00, 0x00}, "BITREV8B R4, R5"},
// loong64enc1.s: ANDN and ORN, two- and three-operand forms.
{[]byte{0xa6, 0x90, 0x16, 0x00}, "ANDN R4, R5, R6"},
{[]byte{0xa5, 0x90, 0x16, 0x00}, "ANDN R4, R5"},
{[]byte{0xa6, 0x10, 0x16, 0x00}, "ORN R4, R5, R6"},
{[]byte{0xa5, 0x10, 0x16, 0x00}, "ORN R4, R5"},
// loong64enc1.s: MULWVW and MULWVWU.
{[]byte{0xa5, 0x10, 0x1f, 0x00}, "MULWVW R4, R5"},
{[]byte{0xa6, 0x10, 0x1f, 0x00}, "MULWVW R4, R5, R6"},
{[]byte{0xa5, 0x90, 0x1f, 0x00}, "MULWVWU R4, R5"},
{[]byte{0xa6, 0x90, 0x1f, 0x00}, "MULWVWU R4, R5, R6"},
// loong64enc1.s: the pointer store and load pair. The
// displacement is the scaled field read the way the toolchain
// takes it back: 32764, not the renderer's -4.
{[]byte{0x85, 0x04, 0x00, 0x25}, "MOVWP R5, 4(R4)"},
{[]byte{0x85, 0x00, 0x00, 0x25}, "MOVWP R5, 0(R4)"},
{[]byte{0x85, 0xfc, 0x7f, 0x25}, "MOVWP R5, 32764(R4)"},
{[]byte{0xa4, 0xfc, 0x7f, 0x24}, "MOVWP 32764(R5), R4"},
{[]byte{0xa4, 0x04, 0x00, 0x24}, "MOVWP 4(R5), R4"},
{[]byte{0xa4, 0x00, 0x00, 0x26}, "MOVVP 0(R5), R4"},
{[]byte{0x85, 0xfc, 0x7f, 0x27}, "MOVVP R5, 32764(R4)"},
// loong64enc1.s: the acquire loads and release stores.
{[]byte{0xa4, 0x80, 0x57, 0x38}, "LLACQW (R5), R4"},
{[]byte{0xa4, 0x88, 0x57, 0x38}, "LLACQV (R5), R4"},
{[]byte{0xc4, 0x14, 0x57, 0x38}, "SCQ R4, R5, (R6)"},
{[]byte{0xc4, 0x84, 0x57, 0x38}, "SCRELW R4, (R6)"},
{[]byte{0xc4, 0x8c, 0x57, 0x38}, "SCRELV R4, (R6)"},
// loong64enc1.s: the floating-point minima and maxima.
{[]byte{0xa6, 0x90, 0x0c, 0x01}, "FMAXAF F4, F5, F6"},
{[]byte{0xa5, 0x90, 0x0c, 0x01}, "FMAXAF F4, F5"},
{[]byte{0xa6, 0x10, 0x0d, 0x01}, "FMAXAD F4, F5, F6"},
{[]byte{0xa6, 0x90, 0x0e, 0x01}, "FMINAF F4, F5, F6"},
{[]byte{0xa6, 0x10, 0x0f, 0x01}, "FMINAD F4, F5, F6"},
// loong64enc1.s: FSEL, FSCALEB and FLOGB.
{[]byte{0x43, 0x04, 0x00, 0x0d}, "FSEL FCC0, F1, F2, F3"},
{[]byte{0x42, 0x84, 0x00, 0x0d}, "FSEL FCC1, F1, F2, F2"},
{[]byte{0xa6, 0x90, 0x10, 0x01}, "FSCALEBF F4, F5, F6"},
{[]byte{0xa6, 0x10, 0x11, 0x01}, "FSCALEBD F4, F5, F6"},
{[]byte{0x85, 0x24, 0x14, 0x01}, "FLOGBF F4, F5"},
{[]byte{0x85, 0x28, 0x14, 0x01}, "FLOGBD F4, F5"},
// loong64enc1.s: PRELD, all three hint rows.
{[]byte{0x80, 0x00, 0xc0, 0x2a}, "PRELD (R4), $0"},
{[]byte{0x88, 0xfc, 0xff, 0x2a}, "PRELD -1(R4), $8"},
{[]byte{0x9f, 0x20, 0xc0, 0x2a}, "PRELD 8(R4), $31"},
// loong64enc1.s: the address-add-and-shift-left family, whose
// rj and rk the renderer prints crossed.
{[]byte{0x86, 0x94, 0x05, 0x00}, "ALSLW $4, R4, R5, R6"},
{[]byte{0x86, 0x94, 0x07, 0x00}, "ALSLWU $4, R4, R5, R6"},
{[]byte{0x86, 0x94, 0x2d, 0x00}, "ALSLV $4, R4, R5, R6"},
} {
ins, err := Decode(arch.LOONG64, 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))
}
}
}
// TestUnknownNamingBoundaries pins what stays untouched: the macro helper
// ADDU16I.D has no toolchain spelling (the assembler never takes it as
// input), so its "Unknown" render is the decoder's own and stays.
func TestUnknownNamingBoundaries(t *testing.T) {
for _, tt := range []struct {
code []byte
text string
}{
{[]byte{0x84, 0x40, 0x00, 0x10}, "Unknown ADDU16I.D $16, R4"},
{[]byte{0x85, 0x00, 0x17, 0x00}, "MOVW R4, R5"},
} {
ins, err := Decode(arch.LOONG64, 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)
}
}
}