// 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 riscv64asm import ( "fmt" "io" "strconv" "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. // The reader text should read from the text segment using text addresses // as offsets; it is used to display pc-relative loads as constant loads. func GoSyntax(inst Inst, pc uint64, symname func(uint64) (string, uint64), text io.ReaderAt) string { if symname == nil { symname = func(uint64) (string, uint64) { return "", 0 } } var args []string for _, a := range inst.Args { if a == nil { break } args = append(args, plan9Arg(&inst, pc, symname, a)) } op := inst.Op.String() switch inst.Op { case AMOADD_D, AMOADD_D_AQ, AMOADD_D_RL, AMOADD_D_AQRL, AMOADD_W, AMOADD_W_AQ, AMOADD_W_RL, AMOADD_W_AQRL, AMOAND_D, AMOAND_D_AQ, AMOAND_D_RL, AMOAND_D_AQRL, AMOAND_W, AMOAND_W_AQ, AMOAND_W_RL, AMOAND_W_AQRL, AMOMAXU_D, AMOMAXU_D_AQ, AMOMAXU_D_RL, AMOMAXU_D_AQRL, AMOMAXU_W, AMOMAXU_W_AQ, AMOMAXU_W_RL, AMOMAXU_W_AQRL, AMOMAX_D, AMOMAX_D_AQ, AMOMAX_D_RL, AMOMAX_D_AQRL, AMOMAX_W, AMOMAX_W_AQ, AMOMAX_W_RL, AMOMAX_W_AQRL, AMOMINU_D, AMOMINU_D_AQ, AMOMINU_D_RL, AMOMINU_D_AQRL, AMOMINU_W, AMOMINU_W_AQ, AMOMINU_W_RL, AMOMINU_W_AQRL, AMOMIN_D, AMOMIN_D_AQ, AMOMIN_D_RL, AMOMIN_D_AQRL, AMOMIN_W, AMOMIN_W_AQ, AMOMIN_W_RL, AMOMIN_W_AQRL, AMOOR_D, AMOOR_D_AQ, AMOOR_D_RL, AMOOR_D_AQRL, AMOOR_W, AMOOR_W_AQ, AMOOR_W_RL, AMOOR_W_AQRL, AMOSWAP_D, AMOSWAP_D_AQ, AMOSWAP_D_RL, AMOSWAP_D_AQRL, AMOSWAP_W, AMOSWAP_W_AQ, AMOSWAP_W_RL, AMOSWAP_W_AQRL, AMOXOR_D, AMOXOR_D_AQ, AMOXOR_D_RL, AMOXOR_D_AQRL, AMOXOR_W, AMOXOR_W_AQ, AMOXOR_W_RL, AMOXOR_W_AQRL, SC_D, SC_D_AQ, SC_D_RL, SC_D_AQRL, SC_W, SC_W_AQ, SC_W_RL, SC_W_AQRL: // Atomic instructions have special operand order. args[2], args[1] = args[1], args[2] case ADDI: if inst.Args[2].(Simm).Imm == 0 { op = "MOV" args = args[:len(args)-1] } case ADDIW: if inst.Args[2].(Simm).Imm == 0 { op = "MOVW" args = args[:len(args)-1] } case ANDI: if inst.Args[2].(Simm).Imm == 255 { op = "MOVBU" args = args[:len(args)-1] } case BEQ: if inst.Args[1].(Reg) == X0 { op = "BEQZ" args[1] = args[2] args = args[:len(args)-1] } for i, j := 0, len(args)-1; i < j; i, j = i+1, j-1 { args[i], args[j] = args[j], args[i] } case BGE: if inst.Args[1].(Reg) == X0 { op = "BGEZ" args[1] = args[2] args = args[:len(args)-1] } for i, j := 0, len(args)-1; i < j; i, j = i+1, j-1 { args[i], args[j] = args[j], args[i] } case BLT: if inst.Args[1].(Reg) == X0 { op = "BLTZ" args[1] = args[2] args = args[:len(args)-1] } for i, j := 0, len(args)-1; i < j; i, j = i+1, j-1 { args[i], args[j] = args[j], args[i] } case BNE: if inst.Args[1].(Reg) == X0 { op = "BNEZ" args[1] = args[2] args = args[:len(args)-1] } for i, j := 0, len(args)-1; i < j; i, j = i+1, j-1 { args[i], args[j] = args[j], args[i] } case BLTU, BGEU: for i, j := 0, len(args)-1; i < j; i, j = i+1, j-1 { args[i], args[j] = args[j], args[i] } case CSRRW: switch inst.Args[1].(CSR) { case FCSR: op = "FSCSR" args[1] = args[2] args = args[:len(args)-1] case FFLAGS: op = "FSFLAGS" args[1] = args[2] args = args[:len(args)-1] case FRM: op = "FSRM" args[1] = args[2] args = args[:len(args)-1] case CYCLE: if inst.Args[0].(Reg) == X0 && inst.Args[2].(Reg) == X0 { op = "UNIMP" args = nil } } case CSRRS: if inst.Args[2].(Reg) == X0 { switch inst.Args[1].(CSR) { case FCSR: op = "FRCSR" args = args[:len(args)-2] case FFLAGS: op = "FRFLAGS" args = args[:len(args)-2] case FRM: op = "FRRM" args = args[:len(args)-2] case CYCLE: op = "RDCYCLE" args = args[:len(args)-2] case CYCLEH: op = "RDCYCLEH" args = args[:len(args)-2] case INSTRET: op = "RDINSTRET" args = args[:len(args)-2] case INSTRETH: op = "RDINSTRETH" args = args[:len(args)-2] case TIME: op = "RDTIME" args = args[:len(args)-2] case TIMEH: op = "RDTIMEH" args = args[:len(args)-2] } } // Fence instruction in plan9 doesn't have any operands. case FENCE: args = nil case FMADD_D, FMADD_H, FMADD_Q, FMADD_S, FMSUB_D, FMSUB_H, FMSUB_Q, FMSUB_S, FNMADD_D, FNMADD_H, FNMADD_Q, FNMADD_S, FNMSUB_D, FNMSUB_H, FNMSUB_Q, FNMSUB_S: args[1], args[3] = args[3], args[1] case FSGNJ_S: if inst.Args[2] == inst.Args[1] { op = "MOVF" args = args[:len(args)-1] } case FSGNJ_D: if inst.Args[2] == inst.Args[1] { op = "MOVD" args = args[:len(args)-1] } case FSGNJX_S: if inst.Args[2] == inst.Args[1] { op = "FABSS" args = args[:len(args)-1] } case FSGNJX_D: if inst.Args[2] == inst.Args[1] { op = "FABSD" args = args[:len(args)-1] } case FSGNJN_S: if inst.Args[2] == inst.Args[1] { op = "FNEGS" args = args[:len(args)-1] } case FSGNJN_D: if inst.Args[2] == inst.Args[1] { op = "FNESD" args = args[:len(args)-1] } case LD, SD: op = "MOV" if inst.Op == SD { args[0], args[1] = args[1], args[0] } case LB, SB: op = "MOVB" if inst.Op == SB { args[0], args[1] = args[1], args[0] } case LH, SH: op = "MOVH" if inst.Op == SH { args[0], args[1] = args[1], args[0] } case LW, SW: op = "MOVW" if inst.Op == SW { args[0], args[1] = args[1], args[0] } case LBU: op = "MOVBU" case LHU: op = "MOVHU" case LWU: op = "MOVWU" case FLW, FSW: op = "MOVF" if inst.Op == FLW { args[0], args[1] = args[1], args[0] } case FLD, FSD: op = "MOVD" if inst.Op == FLD { args[0], args[1] = args[1], args[0] } case SUB: if inst.Args[1].(Reg) == X0 { op = "NEG" args[1] = args[2] args = args[:len(args)-1] } case XORI: if inst.Args[2].(Simm).String() == "-1" { op = "NOT" args = args[:len(args)-1] } case SLTIU: if inst.Args[2].(Simm).Imm == 1 { op = "SEQZ" args = args[:len(args)-1] } case SLTU: if inst.Args[1].(Reg) == X0 { op = "SNEZ" args[1] = args[2] args = args[:len(args)-1] } case JAL: if inst.Args[0].(Reg) == X0 { op = "JMP" args[0] = args[1] args = args[:len(args)-1] } else if inst.Args[0].(Reg) == X1 { op = "CALL" args[0] = args[1] args = args[:len(args)-1] } else { args[0], args[1] = args[1], args[0] } case JALR: if inst.Args[0].(Reg) == X0 { if inst.Args[1].(RegOffset).OfsReg == X1 && inst.Args[1].(RegOffset).Ofs.Imm == 0 { op = "RET" args = nil break } op = "JMP" args[0] = args[1] args = args[:len(args)-1] } else if inst.Args[0].(Reg) == X1 { op = "CALL" args[0] = args[1] args = args[:len(args)-1] } else { args[0], args[1] = args[1], args[0] } } // 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] } // Change to plan9 opcode format // Atomic instructions do not have reorder suffix, so remove them op = strings.Replace(op, ".AQRL", "", -1) op = strings.Replace(op, ".AQ", "", -1) op = strings.Replace(op, ".RL", "", -1) op = strings.Replace(op, ".", "", -1) if args != nil { op += " " + strings.Join(args, ", ") } return op } func plan9Arg(inst *Inst, pc uint64, symname func(uint64) (string, uint64), arg Arg) string { switch a := arg.(type) { case Uimm: return fmt.Sprintf("$%d", uint32(a.Imm)) case Simm: imm, _ := strconv.Atoi(a.String()) if a.Width == 13 || a.Width == 21 { addr := int64(pc) + int64(imm) if s, base := symname(uint64(addr)); s != "" && uint64(addr) == base { return fmt.Sprintf("%s(SB)", s) } return fmt.Sprintf("%d(PC)", imm/4) } return fmt.Sprintf("$%d", int32(imm)) case Reg: if a <= 31 { return fmt.Sprintf("X%d", a) } else { return fmt.Sprintf("F%d", a-32) } case RegOffset: if a.Ofs.Imm == 0 { return fmt.Sprintf("(X%d)", a.OfsReg) } else { return fmt.Sprintf("%s(X%d)", a.Ofs.String(), a.OfsReg) } case AmoReg: return fmt.Sprintf("(X%d)", a.reg) default: return strings.ToUpper(arg.String()) } }