// Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package loong64asm import ( "fmt" "strings" ) // GoSyntax returns the Go assembler syntax for the instruction. // The syntax was originally defined by Plan 9. // The pc is the program counter of the instruction, used for // expanding PC-relative addresses into absolute ones. // The symname function queries the symbol table for the program // being disassembled. Given a target address it returns the name // and base address of the symbol containing the target, if any; // otherwise it returns "", 0. func GoSyntax(inst Inst, pc uint64, symname func(uint64) (string, uint64)) string { if symname == nil { symname = func(uint64) (string, uint64) { return "", 0 } } if inst.Op == 0 && inst.Enc == 0 { return "WORD $0" } else if inst.Op == 0 { return "?" } var args []string for _, a := range inst.Args { if a == nil { break } args = append(args, plan9Arg(&inst, pc, symname, a)) } var op string = plan9OpMap[inst.Op] if op == "" { op = "Unknown " + inst.Op.String() } switch inst.Op { case BSTRPICK_W, BSTRPICK_D, BSTRINS_W, BSTRINS_D: msbw, lsbw := inst.Args[2].(Uimm), inst.Args[3].(Uimm) if inst.Op == BSTRPICK_D && msbw.Imm == 15 && lsbw.Imm == 0 { op = "MOVHU" args = append(args[1:2], args[0:1]...) } else { args[0], args[2], args[3] = args[2], args[3], args[0] } case BCNEZ, BCEQZ: args = args[1:2] case BEQ, BNE: rj := inst.Args[0].(Reg) rd := inst.Args[1].(Reg) if rj == rd && inst.Op == BEQ { op = "JMP" args = args[2:] } else if rj == R0 { args = args[1:] } else if rd == R0 { args = append(args[:1], args[2:]...) } case BEQZ, BNEZ: if inst.Args[0].(Reg) == R0 && inst.Op == BEQ { op = "JMP" args = args[1:] } case BLT, BLTU, BGE, BGEU: rj := inst.Args[0].(Reg) rd := inst.Args[1].(Reg) if rj == rd && (inst.Op == BGE || inst.Op == BGEU) { op = "JMP" args = args[2:] } else if rj == R0 { switch inst.Op { case BGE: op = "BLEZ" case BLT: op = "BGTZ" } args = args[1:] } else if rd == R0 { if !strings.HasSuffix(op, "U") { op += "Z" } args = append(args[:1], args[2:]...) } case JIRL: rd := inst.Args[0].(Reg) rj := inst.Args[1].(Reg) regno := uint16(rj) & 31 off := inst.Args[2].(OffsetSimm).Imm if rd == R0 && rj == R1 && off == 0 { return fmt.Sprintf("RET") } else if rd == R0 && off == 0 { return fmt.Sprintf("JMP (R%d)", regno) } else if rd == R0 { return fmt.Sprintf("JMP %d(R%d)", off, regno) } return fmt.Sprintf("CALL (R%d)", regno) case LD_B, LD_H, LD_W, LD_D, LD_BU, LD_HU, LD_WU, LL_W, LL_D, ST_B, ST_H, ST_W, ST_D, SC_W, SC_D, FLD_S, FLD_D, FST_S, FST_D: var off int32 switch a := inst.Args[2].(type) { case Simm16: off = signumConvInt32(int32(a.Imm), a.Width) case Simm32: off = signumConvInt32(int32(a.Imm), a.Width) >> 2 } Iop := strings.ToUpper(inst.Op.String()) if strings.HasPrefix(Iop, "L") || strings.HasPrefix(Iop, "FL") { return fmt.Sprintf("%s %d(%s), %s", op, off, args[1], args[0]) } return fmt.Sprintf("%s %s, %d(%s)", op, args[0], off, args[1]) case LDX_B, LDX_H, LDX_W, LDX_D, LDX_BU, LDX_HU, LDX_WU, FLDX_S, FLDX_D, STX_B, STX_H, STX_W, STX_D, FSTX_S, FSTX_D: Iop := strings.ToUpper(inst.Op.String()) if strings.HasPrefix(Iop, "L") || strings.HasPrefix(Iop, "FL") { return fmt.Sprintf("%s (%s)(%s), %s", op, args[1], args[2], args[0]) } return fmt.Sprintf("%s %s, (%s)(%s)", op, args[0], args[1], args[2]) case AMADD_B, AMADD_D, AMADD_DB_B, AMADD_DB_D, AMADD_DB_H, AMADD_DB_W, AMADD_H, AMADD_W, AMAND_D, AMAND_DB_D, AMAND_DB_W, AMAND_W, AMCAS_B, AMCAS_D, AMCAS_DB_B, AMCAS_DB_D, AMCAS_DB_H, AMCAS_DB_W, AMCAS_H, AMCAS_W, AMMAX_D, AMMAX_DB_D, AMMAX_DB_DU, AMMAX_DB_W, AMMAX_DB_WU, AMMAX_DU, AMMAX_W, AMMAX_WU, AMMIN_D, AMMIN_DB_D, AMMIN_DB_DU, AMMIN_DB_W, AMMIN_DB_WU, AMMIN_DU, AMMIN_W, AMMIN_WU, AMOR_D, AMOR_DB_D, AMOR_DB_W, AMOR_W, AMSWAP_B, AMSWAP_D, AMSWAP_DB_B, AMSWAP_DB_D, AMSWAP_DB_H, AMSWAP_DB_W, AMSWAP_H, AMSWAP_W, AMXOR_D, AMXOR_DB_D, AMXOR_DB_W, AMXOR_W: return fmt.Sprintf("%s %s, (%s), %s", op, args[1], args[2], args[0]) default: // Reverse args, placing dest last for i, j := 0, len(args)-1; i < j; i, j = i+1, j-1 { args[i], args[j] = args[j], args[i] } switch len(args) { // Special use cases case 0, 1: if inst.Op != B && inst.Op != BL { return op } case 3: switch a0 := inst.Args[0].(type) { case Reg: rj := inst.Args[1].(Reg) if a0 == rj && a0 != R0 { args = args[0:2] } } switch inst.Op { case SUB_W, SUB_D, ADDI_W, ADDI_D, ORI: rj := inst.Args[1].(Reg) if rj == R0 { args = append(args[0:1], args[2:]...) if inst.Op == SUB_W { op = "NEGW" } else if inst.Op == SUB_D { op = "NEGV" } else { op = "MOVW" } } case ANDI: ui12 := inst.Args[2].(Uimm) if ui12.Imm == uint32(0xff) { op = "MOVBU" args = args[1:] } else if ui12.Imm == 0 && inst.Args[0].(Reg) == R0 && inst.Args[1].(Reg) == R0 { return "NOOP" } case SLL_W, OR: rk := inst.Args[2].(Reg) if rk == R0 { args = args[1:] if inst.Op == SLL_W { op = "MOVW" } else { op = "MOVV" } } } } } if args != nil { op += " " + strings.Join(args, ", ") } return op } func plan9Arg(inst *Inst, pc uint64, symname func(uint64) (string, uint64), arg Arg) string { // Reg: gpr[0, 31] and fpr[0, 31] // Fcsr: fcsr[0, 3] // Fcc: fcc[0, 7] // Uimm: unsigned integer constant // Simm16: si16 // Simm32: si32 // OffsetSimm: si32 switch a := arg.(type) { case Reg: regenum := uint16(a) regno := uint16(a) & 0x1f // General-purpose register if regenum >= uint16(R0) && regenum <= uint16(R31) { return fmt.Sprintf("R%d", regno) } else { // Float point register return fmt.Sprintf("F%d", regno) } case Fcsr: regno := uint8(a) & 0x1f return fmt.Sprintf("FCSR%d", regno) case Fcc: regno := uint8(a) & 0x1f return fmt.Sprintf("FCC%d", regno) case Uimm: return fmt.Sprintf("$%d", a.Imm) case Simm16: si16 := signumConvInt32(int32(a.Imm), a.Width) return fmt.Sprintf("$%d", si16) case Simm32: si32 := signumConvInt32(a.Imm, a.Width) return fmt.Sprintf("$%d", si32) case OffsetSimm: offs := offsConvInt32(a.Imm, a.Width) if inst.Op == B || inst.Op == BL { addr := int64(pc) + int64(a.Imm) if s, base := symname(uint64(addr)); s != "" && uint64(addr) == base { return fmt.Sprintf("%s(SB)", s) } } return fmt.Sprintf("%d(PC)", offs>>2) case SaSimm: return fmt.Sprintf("$%d", a) case CodeSimm: return fmt.Sprintf("$%d", a) } return strings.ToUpper(arg.String()) } func signumConvInt32(imm int32, width uint8) int32 { active := uint32(1<> (width - 1)) & 0x1) == 1 { signum |= ^active } return int32(signum) } func offsConvInt32(imm int32, width uint8) int32 { relWidth := width + 2 return signumConvInt32(imm, relWidth) } var plan9OpMap = map[Op]string{ ADD_W: "ADD", ADD_D: "ADDV", SUB_W: "SUB", SUB_D: "SUBV", ADDI_W: "ADD", ADDI_D: "ADDV", LU12I_W: "LU12IW", LU32I_D: "LU32ID", LU52I_D: "LU52ID", SLT: "SGT", SLTU: "SGTU", SLTI: "SGT", SLTUI: "SGTU", PCADDU12I: "PCADDU12I", PCALAU12I: "PCALAU12I", AND: "AND", OR: "OR", NOR: "NOR", XOR: "XOR", ANDI: "AND", ORI: "OR", XORI: "XOR", MUL_W: "MUL", MULH_W: "MULH", MULH_WU: "MULHU", MUL_D: "MULV", MULH_D: "MULHV", MULH_DU: "MULHVU", DIV_W: "DIV", DIV_WU: "DIVU", DIV_D: "DIVV", DIV_DU: "DIVVU", MOD_W: "REM", MOD_WU: "REMU", MOD_D: "REMV", MOD_DU: "REMVU", SLL_W: "SLL", SRL_W: "SRL", SRA_W: "SRA", ROTR_W: "ROTR", SLL_D: "SLLV", SRL_D: "SRLV", SRA_D: "SRAV", ROTR_D: "ROTRV", SLLI_W: "SLL", SRLI_W: "SRL", SRAI_W: "SRA", ROTRI_W: "ROTR", SLLI_D: "SLLV", SRLI_D: "SRLV", SRAI_D: "SRAV", ROTRI_D: "ROTRV", EXT_W_B: "?", EXT_W_H: "?", BITREV_W: "BITREVW", BITREV_D: "BITREVV", CLO_W: "CLOW", CLO_D: "CLOV", CLZ_W: "CLZW", CLZ_D: "CLZV", CTO_W: "CTOW", CTO_D: "CTOV", CTZ_W: "CTZW", CTZ_D: "CTZV", REVB_2H: "REVB2H", REVB_2W: "REVB2W", REVB_4H: "REVB4H", REVB_D: "REVBV", BSTRPICK_W: "BSTRPICKW", BSTRPICK_D: "BSTRPICKV", BSTRINS_W: "BSTRINSW", BSTRINS_D: "BSTRINSV", MASKEQZ: "MASKEQZ", MASKNEZ: "MASKNEZ", BCNEZ: "BFPT", BCEQZ: "BFPF", BEQ: "BEQ", BNE: "BNE", BEQZ: "BEQ", BNEZ: "BNE", BLT: "BLT", BLTU: "BLTU", BGE: "BGE", BGEU: "BGEU", B: "JMP", BL: "CALL", LD_B: "MOVB", LD_H: "MOVH", LD_W: "MOVW", LD_D: "MOVV", LD_BU: "MOVBU", LD_HU: "MOVHU", LD_WU: "MOVWU", ST_B: "MOVB", ST_H: "MOVH", ST_W: "MOVW", ST_D: "MOVV", LDX_B: "MOVB", LDX_BU: "MOVBU", LDX_D: "MOVV", LDX_H: "MOVH", LDX_HU: "MOVHU", LDX_W: "MOVW", LDX_WU: "MOVWU", STX_B: "MOVB", STX_D: "MOVV", STX_H: "MOVH", STX_W: "MOVW", AMADD_B: "AMADDB", AMADD_D: "AMADDV", AMADD_DB_B: "AMADDDBB", AMADD_DB_D: "AMADDDBV", AMADD_DB_H: "AMADDDBH", AMADD_DB_W: "AMADDDBW", AMADD_H: "AMADDH", AMADD_W: "AMADDW", AMAND_D: "AMANDV", AMAND_DB_D: "AMANDDBV", AMAND_DB_W: "AMANDDBW", AMAND_W: "AMANDW", AMCAS_B: "AMCASB", AMCAS_D: "AMCASV", AMCAS_DB_B: "AMCASDBB", AMCAS_DB_D: "AMCASDBV", AMCAS_DB_H: "AMCASDBH", AMCAS_DB_W: "AMCASDBW", AMCAS_H: "AMCASH", AMCAS_W: "AMCASW", AMMAX_D: "AMMAXV", AMMAX_DB_D: "AMMAXDBV", AMMAX_DB_DU: "AMMAXDBVU", AMMAX_DB_W: "AMMAXDBW", AMMAX_DB_WU: "AMMAXDBWU", AMMAX_DU: "AMMAXVU", AMMAX_W: "AMMAXW", AMMAX_WU: "AMMAXWU", AMMIN_D: "AMMINV", AMMIN_DB_D: "AMMINDBV", AMMIN_DB_DU: "AMMINDBVU", AMMIN_DB_W: "AMMINDBW", AMMIN_DB_WU: "AMMINDBWU", AMMIN_DU: "AMMINVU", AMMIN_W: "AMMINW", AMMIN_WU: "AMMINWU", AMOR_D: "AMORV", AMOR_DB_D: "AMORDBV", AMOR_DB_W: "AMORDBW", AMOR_W: "AMORW", AMSWAP_B: "AMSWAPB", AMSWAP_D: "AMSWAPV", AMSWAP_DB_B: "AMSWAPDBB", AMSWAP_DB_D: "AMSWAPDBV", AMSWAP_DB_H: "AMSWAPDBH", AMSWAP_DB_W: "AMSWAPDBW", AMSWAP_H: "AMSWAPH", AMSWAP_W: "AMSWAPW", AMXOR_D: "AMXORV", AMXOR_DB_D: "AMXORDBV", AMXOR_DB_W: "AMXORDBW", AMXOR_W: "AMXORW", LL_W: "LL", LL_D: "LLV", SC_W: "SC", SC_D: "SCV", CRCC_W_B_W: "CRCCWBW", CRCC_W_D_W: "CRCCWVW", CRCC_W_H_W: "CRCCWHW", CRCC_W_W_W: "CRCCWWW", CRC_W_B_W: "CRCWBW", CRC_W_D_W: "CRCWVW", CRC_W_H_W: "CRCWHW", CRC_W_W_W: "CRCWWW", DBAR: "DBAR", SYSCALL: "SYSCALL", BREAK: "BREAK", RDTIMEL_W: "RDTIMELW", RDTIMEH_W: "RDTIMEHW", RDTIME_D: "RDTIMED", CPUCFG: "CPUCFG", // Floating-point instructions FADD_S: "ADDF", FADD_D: "ADDD", FSUB_S: "SUBF", FSUB_D: "SUBD", FMUL_S: "MULF", FMUL_D: "MULD", FDIV_S: "DIVF", FDIV_D: "DIVD", FMSUB_S: "FMSUBF", FMSUB_D: "FMSUBD", FMADD_S: "FMADDF", FMADD_D: "FMADDD", FNMADD_S: "FNMADDF", FNMADD_D: "FNMADDD", FNMSUB_S: "FNMSUBF", FNMSUB_D: "FNMSUBD", FABS_S: "ABSF", FABS_D: "ABSD", FNEG_S: "NEGF", FNEG_D: "NEGD", FSQRT_S: "SQRTF", FSQRT_D: "SQRTD", FCOPYSIGN_S: "FCOPYSGF", FCOPYSIGN_D: "FCOPYSGD", FMAX_S: "FMAXF", FMAX_D: "FMAXD", FMIN_S: "FMINF", FMIN_D: "FMIND", FCLASS_S: "FCLASSF", FCLASS_D: "FCLASSD", FCMP_CEQ_S: "CMPEQF", FCMP_CEQ_D: "CMPEQD", FCMP_SLE_S: "CMPGEF", FCMP_SLE_D: "CMPGED", FCMP_SLT_S: "CMPGTF", FCMP_SLT_D: "CMPGTD", FCVT_D_S: "MOVFD", FCVT_S_D: "MOVDF", FFINT_S_W: "FFINTFW", FFINT_S_L: "FFINTFV", FFINT_D_W: "FFINTDW", FFINT_D_L: "FFINTDV", FTINTRM_L_D: "FTINTRMVD", FTINTRM_L_S: "FTINTRMVF", FTINTRM_W_D: "FTINTRMWD", FTINTRM_W_S: "FTINTRMWF", FTINTRNE_L_D: "FTINTRNEVD", FTINTRNE_L_S: "FTINTRNEVF", FTINTRNE_W_D: "FTINTRNEWD", FTINTRNE_W_S: "FTINTRNEWF", FTINTRP_L_D: "FTINTRPVD", FTINTRP_L_S: "FTINTRPVF", FTINTRP_W_D: "FTINTRPWD", FTINTRP_W_S: "FTINTRPWF", FTINTRZ_L_D: "FTINTRZVD", FTINTRZ_L_S: "FTINTRZVF", FTINTRZ_W_D: "FTINTRZWD", FTINTRZ_W_S: "FTINTRZWF", FTINT_L_D: "FTINTVD", FTINT_L_S: "FTINTVF", FTINT_W_D: "FTINTWD", FTINT_W_S: "FTINTWF", FRINT_S: "FRINTS", FRINT_D: "FRINTD", FMOV_S: "MOVF", FMOV_D: "MOVD", MOVGR2FR_W: "MOVW", MOVGR2FR_D: "MOVV", MOVFR2GR_S: "MOVW", MOVFR2GR_D: "MOVV", MOVGR2CF: "MOVV", MOVCF2GR: "MOVV", MOVFCSR2GR: "MOVV", MOVGR2FCSR: "MOVV", MOVFR2CF: "MOVV", MOVCF2FR: "MOVV", FLD_S: "MOVF", FLD_D: "MOVD", FST_S: "MOVF", FST_D: "MOVD", FLDX_S: "MOVF", FLDX_D: "MOVD", FSTX_S: "MOVF", FSTX_D: "MOVD", }