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
112 lines
4.6 KiB
Go
112 lines
4.6 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"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
}
|