// Copyright 2017 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 arm64asm import ( "fmt" "io" "sort" "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 LDR, LDRB, LDRH, LDRSB, LDRSH, LDRSW: // Check for PC-relative load. if offset, ok := inst.Args[1].(PCRel); ok { addr := pc + uint64(offset) if _, ok := inst.Args[0].(Reg); !ok { break } if s, base := symname(addr); s != "" && addr == base { args[1] = fmt.Sprintf("$%s(SB)", s) } } } // Move addressing mode into opcode suffix. suffix := "" switch inst.Op { case LDR, LDRB, LDRH, LDRSB, LDRSH, LDRSW, STR, STRB, STRH, STUR, STURB, STURH, LD1, ST1: switch mem := inst.Args[1].(type) { case MemImmediate: switch mem.Mode { case AddrOffset: // no suffix case AddrPreIndex: suffix = ".W" case AddrPostIndex, AddrPostReg: suffix = ".P" } } case STP, LDP: switch mem := inst.Args[2].(type) { case MemImmediate: switch mem.Mode { case AddrOffset: // no suffix case AddrPreIndex: suffix = ".W" case AddrPostIndex: suffix = ".P" } } } switch inst.Op { case BL: return "CALL " + args[0] case BLR: r := inst.Args[0].(Reg) regno := uint16(r) & 31 return fmt.Sprintf("CALL (R%d)", regno) case RET: if r, ok := inst.Args[0].(Reg); ok && r == X30 { return "RET" } case B: if cond, ok := inst.Args[0].(Cond); ok { return "B" + cond.String() + " " + args[1] } return "JMP" + " " + args[0] case BR: r := inst.Args[0].(Reg) regno := uint16(r) & 31 return fmt.Sprintf("JMP (R%d)", regno) case MOV: rno := -1 switch a := inst.Args[0].(type) { case Reg: rno = int(a) case RegSP: rno = int(a) case RegisterWithArrangementAndIndex: op = "VMOV" case RegisterWithArrangement: op = "VMOV" } if rno >= 0 && rno <= int(WZR) { op = "MOVW" } else if rno >= int(X0) && rno <= int(XZR) { op = "MOVD" } if _, ok := inst.Args[1].(RegisterWithArrangementAndIndex); ok { op = "VMOV" } case LDR, LDUR: var rno uint16 if r, ok := inst.Args[0].(Reg); ok { rno = uint16(r) } else { rno = uint16(inst.Args[0].(RegSP)) } if rno <= uint16(WZR) { op = "MOVWU" + suffix } else if rno >= uint16(B0) && rno <= uint16(B31) { op = "FMOVB" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else if rno >= uint16(H0) && rno <= uint16(H31) { op = "FMOVH" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else if rno >= uint16(S0) && rno <= uint16(S31) { op = "FMOVS" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else if rno >= uint16(D0) && rno <= uint16(D31) { op = "FMOVD" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else if rno >= uint16(Q0) && rno <= uint16(Q31) { op = "FMOVQ" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else { op = "MOVD" + suffix } case LDRB: op = "MOVBU" + suffix case LDRH: op = "MOVHU" + suffix case LDRSW: op = "MOVW" + suffix case LDRSB: if r, ok := inst.Args[0].(Reg); ok { rno := uint16(r) if rno <= uint16(WZR) { op = "MOVBW" + suffix } else { op = "MOVB" + suffix } } case LDRSH: if r, ok := inst.Args[0].(Reg); ok { rno := uint16(r) if rno <= uint16(WZR) { op = "MOVHW" + suffix } else { op = "MOVH" + suffix } } case STR, STUR: var rno uint16 if r, ok := inst.Args[0].(Reg); ok { rno = uint16(r) } else { rno = uint16(inst.Args[0].(RegSP)) } if rno <= uint16(WZR) { op = "MOVW" + suffix } else if rno >= uint16(B0) && rno <= uint16(B31) { op = "FMOVB" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else if rno >= uint16(H0) && rno <= uint16(H31) { op = "FMOVH" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else if rno >= uint16(S0) && rno <= uint16(S31) { op = "FMOVS" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else if rno >= uint16(D0) && rno <= uint16(D31) { op = "FMOVD" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else if rno >= uint16(Q0) && rno <= uint16(Q31) { op = "FMOVQ" + suffix args[0] = fmt.Sprintf("F%d", rno&31) } else { op = "MOVD" + suffix } args[0], args[1] = args[1], args[0] case STRB, STURB: op = "MOVB" + suffix args[0], args[1] = args[1], args[0] case STRH, STURH: op = "MOVH" + suffix args[0], args[1] = args[1], args[0] case TBNZ, TBZ: args[0], args[1], args[2] = args[2], args[0], args[1] case MADD, MSUB, SMADDL, SMSUBL, UMADDL, UMSUBL: if r, ok := inst.Args[0].(Reg); ok { rno := uint16(r) if rno <= uint16(WZR) { op += "W" } } args[2], args[3] = args[3], args[2] case STLR: if r, ok := inst.Args[0].(Reg); ok { rno := uint16(r) if rno <= uint16(WZR) { op += "W" } } args[0], args[1] = args[1], args[0] case STLRB, STLRH: args[0], args[1] = args[1], args[0] case STLXR, STXR: if r, ok := inst.Args[1].(Reg); ok { rno := uint16(r) if rno <= uint16(WZR) { op += "W" } } args[1], args[2] = args[2], args[1] case STLXRB, STLXRH, STXRB, STXRH: args[1], args[2] = args[2], args[1] case BFI, BFXIL, SBFIZ, SBFX, UBFIZ, UBFX: if r, ok := inst.Args[0].(Reg); ok { rno := uint16(r) if rno <= uint16(WZR) { op += "W" } } args[1], args[2], args[3] = args[3], args[1], args[2] case LDAXP, LDXP: if r, ok := inst.Args[0].(Reg); ok { rno := uint16(r) if rno <= uint16(WZR) { op += "W" } } args[0] = fmt.Sprintf("(%s, %s)", args[0], args[1]) args[1] = args[2] return op + " " + args[1] + ", " + args[0] case STP, LDP: args[0] = fmt.Sprintf("(%s, %s)", args[0], args[1]) args[1] = args[2] rno, ok := inst.Args[0].(Reg) if !ok { rno = Reg(inst.Args[0].(RegSP)) } if rno <= WZR { op = op + "W" } else if rno >= S0 && rno <= S31 { op = "F" + op + "S" } else if rno >= D0 && rno <= D31 { op = "F" + op + "D" } else if rno >= Q0 && rno <= Q31 { op = "F" + op + "Q" } op = op + suffix if inst.Op.String() == "STP" { return op + " " + args[0] + ", " + args[1] } else { return op + " " + args[1] + ", " + args[0] } case STLXP, STXP: if r, ok := inst.Args[1].(Reg); ok { rno := uint16(r) if rno <= uint16(WZR) { op += "W" } } args[1] = fmt.Sprintf("(%s, %s)", args[1], args[2]) args[2] = args[3] return op + " " + args[1] + ", " + args[2] + ", " + args[0] case FCCMP, FCCMPE: args[0], args[1] = args[1], args[0] fallthrough case FCMP, FCMPE: if _, ok := inst.Args[1].(Imm); ok { args[1] = "$(0.0)" } fallthrough case FADD, FSUB, FMUL, FNMUL, FDIV, FMAX, FMIN, FMAXNM, FMINNM, FCSEL, FMADD, FMSUB, FNMADD, FNMSUB: if strings.HasSuffix(op, "MADD") || strings.HasSuffix(op, "MSUB") { args[2], args[3] = args[3], args[2] } if r, ok := inst.Args[0].(Reg); ok { rno := uint16(r) if rno >= uint16(S0) && rno <= uint16(S31) { op = fmt.Sprintf("%sS", op) } else if rno >= uint16(D0) && rno <= uint16(D31) { op = fmt.Sprintf("%sD", op) } } case FCVT: for i := 1; i >= 0; i-- { if r, ok := inst.Args[i].(Reg); ok { rno := uint16(r) if rno >= uint16(H0) && rno <= uint16(H31) { op = fmt.Sprintf("%sH", op) } else if rno >= uint16(S0) && rno <= uint16(S31) { op = fmt.Sprintf("%sS", op) } else if rno >= uint16(D0) && rno <= uint16(D31) { op = fmt.Sprintf("%sD", op) } } } case FABS, FNEG, FSQRT, FRINTN, FRINTP, FRINTM, FRINTZ, FRINTA, FRINTX, FRINTI: if r, ok := inst.Args[1].(Reg); ok { rno := uint16(r) if rno >= uint16(S0) && rno <= uint16(S31) { op = fmt.Sprintf("%sS", op) } else if rno >= uint16(D0) && rno <= uint16(D31) { op = fmt.Sprintf("%sD", op) } } case FCVTZS, FCVTZU, SCVTF, UCVTF: if _, ok := inst.Args[2].(Imm); !ok { for i := 1; i >= 0; i-- { if r, ok := inst.Args[i].(Reg); ok { rno := uint16(r) if rno >= uint16(S0) && rno <= uint16(S31) { op = fmt.Sprintf("%sS", op) } else if rno >= uint16(D0) && rno <= uint16(D31) { op = fmt.Sprintf("%sD", op) } else if rno <= uint16(WZR) { op += "W" } } } } case FMOV: for i := 0; i <= 1; i++ { if r, ok := inst.Args[i].(Reg); ok { rno := uint16(r) if rno >= uint16(S0) && rno <= uint16(S31) { op = fmt.Sprintf("%sS", op) break } else if rno >= uint16(D0) && rno <= uint16(D31) { op = fmt.Sprintf("%sD", op) break } } } case SYSL: op1 := int(inst.Args[1].(Imm).Imm) cn := int(inst.Args[2].(Imm_c)) cm := int(inst.Args[3].(Imm_c)) op2 := int(inst.Args[4].(Imm).Imm) sysregno := int32(op1<<16 | cn<<12 | cm<<8 | op2<<5) args[1] = fmt.Sprintf("$%d", sysregno) return op + " " + args[1] + ", " + args[0] case CBNZ, CBZ: if r, ok := inst.Args[0].(Reg); ok { rno := uint16(r) if rno <= uint16(WZR) { op += "W" } } args[0], args[1] = args[1], args[0] case ADR, ADRP: addr := int64(inst.Args[1].(PCRel)) args[1] = fmt.Sprintf("%d(PC)", addr) case MSR: args[0] = inst.Args[0].String() case ST1: op = fmt.Sprintf("V%s", op) + suffix args[0], args[1] = args[1], args[0] case LD1: op = fmt.Sprintf("V%s", op) + suffix case UMOV: op = "VMOV" case NOP: op = "NOOP" default: index := sort.SearchStrings(noSuffixOpSet, op) if !(index < len(noSuffixOpSet) && noSuffixOpSet[index] == op) { rno := -1 switch a := inst.Args[0].(type) { case Reg: rno = int(a) case RegSP: rno = int(a) case RegisterWithArrangement: op = fmt.Sprintf("V%s", op) } if rno >= int(B0) && rno <= int(Q31) && !strings.HasPrefix(op, "F") { op = fmt.Sprintf("V%s", op) } if rno >= 0 && rno <= int(WZR) { // Add "w" to opcode suffix. op += "W" } } op = op + suffix } // conditional instructions, replace args. if _, ok := inst.Args[3].(Cond); ok { if _, ok := inst.Args[2].(Reg); ok { args[1], args[2] = args[2], args[1] } else { args[0], args[2] = args[2], 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] } if args != nil { op += " " + strings.Join(args, ", ") } return op } // No need add "W" to opcode suffix. // Opcode must be inserted in ascending order. var noSuffixOpSet = strings.Fields(` AESD AESE AESIMC AESMC CRC32B CRC32CB CRC32CH CRC32CW CRC32CX CRC32H CRC32W CRC32X LDARB LDARH LDAXRB LDAXRH LDTRH LDXRB LDXRH SHA1C SHA1H SHA1M SHA1P SHA1SU0 SHA1SU1 SHA256H SHA256H2 SHA256SU0 SHA256SU1 `) // floating point instructions without "F" prefix. var fOpsWithoutFPrefix = map[Op]bool{ LDP: true, STP: true, } func plan9Arg(inst *Inst, pc uint64, symname func(uint64) (string, uint64), arg Arg) string { switch a := arg.(type) { case Imm: return fmt.Sprintf("$%d", uint32(a.Imm)) case Imm64: return fmt.Sprintf("$%d", int64(a.Imm)) case ImmShift: if a.shift == 0 { return fmt.Sprintf("$%d", a.imm) } return fmt.Sprintf("$(%d<<%d)", a.imm, a.shift) case PCRel: addr := int64(pc) + int64(a) if s, base := symname(uint64(addr)); s != "" && uint64(addr) == base { return fmt.Sprintf("%s(SB)", s) } return fmt.Sprintf("%d(PC)", a/4) case Reg: regenum := uint16(a) regno := uint16(a) & 31 if regenum >= uint16(B0) && regenum <= uint16(Q31) { if strings.HasPrefix(inst.Op.String(), "F") || strings.HasSuffix(inst.Op.String(), "CVTF") || fOpsWithoutFPrefix[inst.Op] { // FP registers are the same ones as SIMD registers // Print Fn for scalar variant to align with assembler (e.g., FCVT, SCVTF, UCVTF, etc.) return fmt.Sprintf("F%d", regno) } else { // Print Vn to align with assembler (e.g., SHA256H) return fmt.Sprintf("V%d", regno) } } return plan9gpr(a) case RegSP: regno := uint16(a) & 31 if regno == 31 { return "RSP" } return fmt.Sprintf("R%d", regno) case RegExtshiftAmount: reg := plan9gpr(a.reg) extshift := "" amount := "" if a.extShift != ExtShift(0) { switch a.extShift { default: extshift = "." + a.extShift.String() case lsl: extshift = "<<" amount = fmt.Sprintf("%d", a.amount) return reg + extshift + amount case lsr: extshift = ">>" amount = fmt.Sprintf("%d", a.amount) return reg + extshift + amount case asr: extshift = "->" amount = fmt.Sprintf("%d", a.amount) return reg + extshift + amount case ror: extshift = "@>" amount = fmt.Sprintf("%d", a.amount) return reg + extshift + amount } if a.amount != 0 { amount = fmt.Sprintf("<<%d", a.amount) } } return reg + extshift + amount case MemImmediate: off := "" base := "" regno := uint16(a.Base) & 31 if regno == 31 { base = "(RSP)" } else { base = fmt.Sprintf("(R%d)", regno) } if a.imm != 0 && a.Mode != AddrPostReg { off = fmt.Sprintf("%d", a.imm) } else if a.Mode == AddrPostReg { postR := fmt.Sprintf("(R%d)", a.imm) return base + postR } return off + base case MemExtend: base := "" index := "" regno := uint16(a.Base) & 31 if regno == 31 { base = "(RSP)" } else { base = fmt.Sprintf("(R%d)", regno) } indexreg := plan9gpr(a.Index) if a.Extend == lsl { // Refer to ARM reference manual, for byte load/store(register), the index // shift amount must be 0, encoded in "S" as 0 if omitted, or as 1 if present. // a.Amount indicates the index shift amount, encoded in "S" field. // a.ShiftMustBeZero is set true indicates the index shift amount must be 0. // When a.ShiftMustBeZero is true, GNU syntax prints "[Xn, Xm lsl #0]" if "S" // equals to 1, or prints "[Xn, Xm]" if "S" equals to 0. if a.Amount != 0 && !a.ShiftMustBeZero { index = fmt.Sprintf("(%s<<%d)", indexreg, a.Amount) } else if a.ShiftMustBeZero && a.Amount == 1 { // When a.ShiftMustBeZero is ture, Go syntax prints "(Rm<<0)" if "a.Amount" // equals to 1. index = fmt.Sprintf("(%s<<0)", indexreg) } else { index = fmt.Sprintf("(%s)", indexreg) } } else { if a.Amount != 0 && !a.ShiftMustBeZero { index = fmt.Sprintf("(%s.%s<<%d)", indexreg, a.Extend.String(), a.Amount) } else { index = fmt.Sprintf("(%s.%s)", indexreg, a.Extend.String()) } } return base + index case Cond: switch arg.String() { case "CS": return "HS" case "CC": return "LO" } case Imm_clrex: return fmt.Sprintf("$%d", uint32(a)) case Imm_dcps: return fmt.Sprintf("$%d", uint32(a)) case Imm_option: return fmt.Sprintf("$%d", uint8(a)) case Imm_hint: return fmt.Sprintf("$%d", uint8(a)) case Imm_fp: var s, pre, numerator, denominator int16 var result float64 if a.s == 0 { s = 1 } else { s = -1 } pre = s * int16(16+a.pre) if a.exp > 0 { numerator = (pre << uint8(a.exp)) denominator = 16 } else { numerator = pre denominator = (16 << uint8(-1*a.exp)) } result = float64(numerator) / float64(denominator) return strings.TrimRight(fmt.Sprintf("$%f", result), "0") case RegisterWithArrangement: result := a.r.String() arrange := a.a.String() c := []rune(arrange) switch len(c) { case 3: c[1], c[2] = c[2], c[1] // .8B -> .B8 case 4: c[1], c[2], c[3] = c[3], c[1], c[2] // 16B -> B16 } arrange = string(c) result += arrange if a.cnt > 0 { result = "[" + result for i := 1; i < int(a.cnt); i++ { cur := V0 + Reg((uint16(a.r)-uint16(V0)+uint16(i))&31) result += ", " + cur.String() + arrange } result += "]" } return result case RegisterWithArrangementAndIndex: result := a.r.String() arrange := a.a.String() result += arrange if a.cnt > 1 { result = "[" + result for i := 1; i < int(a.cnt); i++ { cur := V0 + Reg((uint16(a.r)-uint16(V0)+uint16(i))&31) result += ", " + cur.String() + arrange } result += "]" } return fmt.Sprintf("%s[%d]", result, a.index) case Systemreg: return fmt.Sprintf("$%d", uint32(a.op0&1)<<14|uint32(a.op1&7)<<11|uint32(a.cn&15)<<7|uint32(a.cm&15)<<3|uint32(a.op2)&7) case Imm_prfop: if strings.Contains(a.String(), "#") { return fmt.Sprintf("$%d", a) } case sysOp: result := a.op.String() if a.r != 0 { result += ", " + plan9gpr(a.r) } return result } return strings.ToUpper(arg.String()) } // Convert a general-purpose register to plan9 assembly format. func plan9gpr(r Reg) string { regno := uint16(r) & 31 if regno == 31 { return "ZR" } return fmt.Sprintf("R%d", regno) }