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

+140
View File
@@ -0,0 +1,140 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package disasm
import (
"fmt"
"golang.org/x/arch/loong64/loong64asm"
)
// golang.org/x/arch's Plan 9 renderer leaves two holes over instructions
// it decodes perfectly well: a set of operations absent from its op-name
// table, which it renders as "Unknown OP.ARGS", and the sign-extension
// pair EXT.W.B/EXT.W.H, whose table entries are the literal "?" while the
// operands print fine. The toolchain's assembler corpus carries every one
// of these families with machine-checked encodings and its own spellings
// (loong64enc1.s's "EXTWB R4, R5 // 855c0000" and its kin), so the
// disassembler re-renders them after the decode: the operations whose
// operand order x/arch already prints the Plan 9 way keep their operand
// text and trade only the mnemonic, and the handful whose order differs
// (the pointer loads and stores, the acquire loads, the release stores,
// PRELD and ALSL) are rebuilt from the decoded arguments.
// loong64OpNames maps the decoder operations rendered as "Unknown OPS" to
// the toolchain's Plan 9 mnemonics. The operand text x/arch prints for
// these is already the Plan 9 order; only the name is replaced.
var loong64OpNames = map[loong64asm.Op]string{
loong64asm.REVH_2W: "REVH2W",
loong64asm.REVH_D: "REVHV",
loong64asm.BITREV_4B: "BITREV4B",
loong64asm.BITREV_8B: "BITREV8B",
loong64asm.ANDN: "ANDN",
loong64asm.ORN: "ORN",
loong64asm.MULW_D_W: "MULWVW",
loong64asm.MULW_D_WU: "MULWVWU",
loong64asm.FMAXA_S: "FMAXAF",
loong64asm.FMAXA_D: "FMAXAD",
loong64asm.FMINA_S: "FMINAF",
loong64asm.FMINA_D: "FMINAD",
loong64asm.FSCALEB_S: "FSCALEBF",
loong64asm.FSCALEB_D: "FSCALEBD",
loong64asm.FLOGB_S: "FLOGBF",
loong64asm.FLOGB_D: "FLOGBD",
loong64asm.FSEL: "FSEL",
}
// nameLoong64 re-renders the instruction text for the families above. It
// takes the decoded instruction and the renderer's own text and returns the
// text to print; untouched families return text unchanged.
func nameLoong64(inst loong64asm.Inst, text string) string {
// The operations whose operand order the renderer prints wrong, plus
// the two it names "?": rebuild the text from the arguments.
switch inst.Op {
case loong64asm.EXT_W_B, loong64asm.EXT_W_H:
name := "EXTWB"
if inst.Op == loong64asm.EXT_W_H {
name = "EXTWH"
}
return fmt.Sprintf("%s %s, %s", name, loong64Reg(inst, 1), loong64Reg(inst, 0))
case loong64asm.STPTR_W, loong64asm.STPTR_D:
name := "MOVWP"
if inst.Op == loong64asm.STPTR_D {
name = "MOVVP"
}
return fmt.Sprintf("%s %s, %s(%s)", name, loong64Reg(inst, 0), loong64Disp(inst, 2), loong64Reg(inst, 1))
case loong64asm.LDPTR_W, loong64asm.LDPTR_D:
name := "MOVWP"
if inst.Op == loong64asm.LDPTR_D {
name = "MOVVP"
}
return fmt.Sprintf("%s %s(%s), %s", name, loong64Disp(inst, 2), loong64Reg(inst, 1), loong64Reg(inst, 0))
case loong64asm.LLACQ_W, loong64asm.LLACQ_D:
name := "LLACQW"
if inst.Op == loong64asm.LLACQ_D {
name = "LLACQV"
}
return fmt.Sprintf("%s (%s), %s", name, loong64Reg(inst, 1), loong64Reg(inst, 0))
case loong64asm.SC_Q:
return fmt.Sprintf("SCQ %s, %s, (%s)", loong64Reg(inst, 0), loong64Reg(inst, 1), loong64Reg(inst, 2))
case loong64asm.SCREL_W, loong64asm.SCREL_D:
name := "SCRELW"
if inst.Op == loong64asm.SCREL_D {
name = "SCRELV"
}
return fmt.Sprintf("%s %s, (%s)", name, loong64Reg(inst, 0), loong64Reg(inst, 1))
case loong64asm.PRELD:
disp := int32(inst.Args[2].(loong64asm.Simm16).Imm)
hint := inst.Args[0].(loong64asm.Uimm).Imm
if disp == 0 {
return fmt.Sprintf("PRELD (%s), $%d", loong64Reg(inst, 1), hint)
}
return fmt.Sprintf("PRELD %d(%s), $%d", disp, loong64Reg(inst, 1), hint)
case loong64asm.ALSL_W, loong64asm.ALSL_WU, loong64asm.ALSL_D:
name := "ALSLW"
switch inst.Op {
case loong64asm.ALSL_WU:
name = "ALSLWU"
case loong64asm.ALSL_D:
name = "ALSLV"
}
return fmt.Sprintf("%s $%d, %s, %s, %s", name, inst.Args[3].(loong64asm.SaSimm),
loong64Reg(inst, 1), loong64Reg(inst, 2), loong64Reg(inst, 0))
}
// The name-only families: replace the "Unknown OPS" prefix, keeping
// the operand text.
if name, ok := loong64OpNames[inst.Op]; ok {
prefix := "Unknown " + inst.Op.String()
if rest, found := trimSpacePrefix(text, prefix); found {
return name + rest
}
}
return text
}
// loong64Reg renders the general-purpose register of argument i, which the
// families here all use; the decoder stores the plain register number.
func loong64Reg(inst loong64asm.Inst, i int) string {
return fmt.Sprintf("R%d", int(inst.Args[i].(loong64asm.Reg))&31)
}
// loong64Disp renders the displacement of argument i. The pointer
// loads and stores scale their immediate by four: the decoder hands back
// the unscaled field as a 16-bit quantity (32764, say, whose field the
// assembler wrote from 32764 >> 2), and the round-tripping spelling is
// the same quantity read as signed 16-bit, the way the toolchain's own
// assembler takes it back: 32764 keeps the positive reading and the
// fields with the sign bit set render negative (-4, not 65532).
func loong64Disp(inst loong64asm.Inst, i int) string {
return fmt.Sprintf("%d", int16(inst.Args[i].(loong64asm.Simm32).Imm))
}
// trimSpacePrefix reports the text beyond prefix plus its separating
// space, and whether prefix really opened text.
func trimSpacePrefix(text, prefix string) (string, bool) {
if len(text) > len(prefix) && text[:len(prefix)] == prefix && text[len(prefix)] == ' ' {
return text[len(prefix):], true
}
return text, false
}