140 lines
5.5 KiB
Go
140 lines
5.5 KiB
Go
// 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
|
||
|
|
}
|