// Copyright (c) 2026 Petr BalvĂ­n (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 }