| // Copyright 2014 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 ppc64asm |
| |
| import ( |
| "bytes" |
| "fmt" |
| "strings" |
| ) |
| |
| var ( |
| // bit 3 of index is a negated check. |
| condBit = [8]string{ |
| "lt", "gt", "eq", "so", |
| "ge", "le", "ne", "ns"} |
| ) |
| |
| // GNUSyntax returns the GNU assembler syntax for the instruction, as defined by GNU binutils. |
| // This form typically matches the syntax defined in the Power ISA Reference Manual. |
| func GNUSyntax(inst Inst, pc uint64) string { |
| var buf bytes.Buffer |
| // When there are all 0s, identify them as the disassembler |
| // in binutils would. |
| if inst.Enc == 0 { |
| return ".long 0x0" |
| } else if inst.Op == 0 { |
| return "error: unknown instruction" |
| } |
| |
| PC := pc |
| // Special handling for some ops |
| startArg := 0 |
| sep := " " |
| opName := inst.Op.String() |
| argList := inst.Args[:] |
| |
| switch opName { |
| case "bc", "bcl", "bca", "bcla", "bclr", "bclrl", "bcctr", "bcctrl", "bctar", "bctarl": |
| sfx := inst.Op.String()[2:] |
| bo := int(inst.Args[0].(Imm)) |
| bi := inst.Args[1].(CondReg) |
| atsfx := [4]string{"", "?", "-", "+"} |
| decsfx := [2]string{"dnz", "dz"} |
| |
| //BO field is... complicated (z == ignored bit, at == prediction hint) |
| //Paraphrased from ISA 3.1 Book I Section 2.4: |
| // |
| //0000z -> decrement ctr, b if ctr != 0 and CRbi == 0 |
| //0001z -> decrement ctr, b if ctr == 0 and CRbi == 0 |
| //001at -> b if CRbi == 0 |
| //0100z -> decrement ctr, b if ctr != 0 and CRbi == 1 |
| //0101z -> decrement ctr, b if ctr == 0 and CRbi == 1 |
| //011at -> b if CRbi == 1 |
| //1a00t -> decrement ctr, b if ctr != 0 |
| //1a01t -> decrement ctr, b if ctr == 0 |
| //1z1zz -> b always |
| |
| // Decoding (in this order) we get |
| // BO & 0b00100 == 0b00000 -> dz if BO[1], else dnz (not simplified for bcctrl forms) |
| // BO & 0b10000 == 0b10000 -> (bc and bca forms not simplified), at = B[4]B[0] if B[2] != 0, done |
| // BO & 0b10000 == 0b00000 -> t if BO[3], else f |
| // BO & 0b10100 == 0b00100 -> at = B[0:1] |
| |
| // BI fields rename as follows: |
| // less than : lt BI%4==0 && test == t |
| // less than or equal : le BI%4==1 && test == f |
| // equal : eq BI%4==2 && test == t |
| // greater than or equal: ge BI%4==0 && test == f |
| // greater than : gt BI%4==1 && test == t |
| // not less than : nl BI%4==0 && test == f |
| // not equal : ne BI%4==2 && test == f |
| // not greater than : ng BI%4==1 && test == f |
| // summary overflow : so BI%4==3 && test == t |
| // not summary overflow : ns BI%4==3 && test == f |
| // unordered : un BI%4==3 && test == t |
| // not unordered : nu BI%4==3 && test == f |
| // |
| // Note, there are only 8 possible tests, but quite a few more |
| // ways to name fields. For simplicity, we choose those in condBit. |
| |
| at := 0 // 0 == no hint, 1 == reserved, 2 == not likely, 3 == likely |
| form := 1 // 1 == n/a, 0 == cr bit not set, 4 == cr bit set |
| cr := (bi - Cond0LT) / 4 |
| bh := -1 // Only for lr/tar/ctr variants. |
| switch opName { |
| case "bclr", "bclrl", "bcctr", "bcctrl", "bctar", "bctarl": |
| bh = int(inst.Args[2].(Imm)) |
| } |
| |
| if bo&0x14 == 0x14 { |
| if bo == 0x14 && bi == Cond0LT { // preferred form of unconditional branch |
| // Likewise, avoid printing fake b/ba/bl/bla |
| if opName != "bc" && opName != "bca" && opName != "bcl" && opName != "bcla" { |
| startArg = 2 |
| } |
| } |
| } else if bo&0x04 == 0 { // ctr is decremented |
| if opName != "bcctr" && opName != "bcctrl" { |
| startArg = 1 |
| tf := "" |
| if bo&0x10 == 0x00 { |
| tf = "f" |
| if bo&0x08 == 0x08 { |
| tf = "t" |
| } |
| } |
| sfx = decsfx[(bo>>1)&1] + tf + sfx |
| } |
| if bo&0x10 == 0x10 { |
| if opName != "bcctr" && opName != "bcctrl" { |
| startArg = 2 |
| } |
| if bi != Cond0LT { |
| // A non-zero BI bit was encoded, but ignored by BO |
| startArg = 0 |
| } |
| at = ((bo & 0x8) >> 2) | (bo & 0x1) |
| } else if bo&0x4 == 0x4 { |
| at = bo & 0x3 |
| } |
| } else if bo&0x10 == 0x10 { // BI field is not used |
| if opName != "bca" && opName != "bc" { |
| at = ((bo & 0x8) >> 2) | (bo & 0x1) |
| startArg = 2 |
| } |
| // If BI is encoded as a bit other than 0, no mnemonic. |
| if bo&0x14 == 0x14 { |
| startArg = 0 |
| } |
| } else { |
| form = (bo & 0x8) >> 1 |
| startArg = 2 |
| if bo&0x14 == 0x04 { |
| at = bo & 0x3 |
| } |
| } |
| sfx += atsfx[at] |
| |
| if form != 1 { |
| bit := int((bi-Cond0LT)%4) | (^form)&0x4 |
| sfx = condBit[bit] + sfx |
| } |
| |
| if at != 1 && startArg > 0 && bh <= 0 { |
| str := fmt.Sprintf("b%s", sfx) |
| if startArg > 1 && (cr != 0 || bh > 0) { |
| str += fmt.Sprintf(" cr%d", cr) |
| sep = "," |
| } |
| buf.WriteString(str) |
| if startArg < 2 && bh == 0 { |
| str := fmt.Sprintf(" %s", |
| gnuArg(&inst, 1, inst.Args[1], PC)) |
| buf.WriteString(str) |
| startArg = 3 |
| } else if bh == 0 { |
| startArg = 3 |
| } |
| } else { |
| if startArg == 0 || bh > 0 || at == 1 { |
| buf.WriteString(inst.Op.String()) |
| buf.WriteString(atsfx[at]) |
| startArg = 0 |
| } else { |
| buf.WriteString("b" + sfx) |
| } |
| if bh == 0 { |
| str := fmt.Sprintf(" %d,%s", bo, gnuArg(&inst, 1, inst.Args[1], PC)) |
| buf.WriteString(str) |
| startArg = 3 |
| } |
| } |
| |
| case "mtspr": |
| opcode := inst.Op.String() |
| buf.WriteString(opcode[0:2]) |
| switch spr := inst.Args[0].(type) { |
| case SpReg: |
| switch spr { |
| case 1: |
| buf.WriteString("xer") |
| startArg = 1 |
| case 8: |
| buf.WriteString("lr") |
| startArg = 1 |
| case 9: |
| buf.WriteString("ctr") |
| startArg = 1 |
| default: |
| buf.WriteString("spr") |
| } |
| default: |
| buf.WriteString("spr") |
| } |
| |
| case "mfspr": |
| opcode := inst.Op.String() |
| buf.WriteString(opcode[0:2]) |
| arg := inst.Args[0] |
| switch spr := inst.Args[1].(type) { |
| case SpReg: |
| switch spr { |
| case 1: |
| buf.WriteString("xer ") |
| buf.WriteString(gnuArg(&inst, 0, arg, PC)) |
| startArg = 2 |
| case 8: |
| buf.WriteString("lr ") |
| buf.WriteString(gnuArg(&inst, 0, arg, PC)) |
| startArg = 2 |
| case 9: |
| buf.WriteString("ctr ") |
| buf.WriteString(gnuArg(&inst, 0, arg, PC)) |
| startArg = 2 |
| case 268: |
| buf.WriteString("tb ") |
| buf.WriteString(gnuArg(&inst, 0, arg, PC)) |
| startArg = 2 |
| default: |
| buf.WriteString("spr") |
| } |
| default: |
| buf.WriteString("spr") |
| } |
| |
| case "mtfsfi", "mtfsfi.": |
| buf.WriteString(opName) |
| l := inst.Args[2].(Imm) |
| if l == 0 { |
| // L == 0 is an extended mnemonic for the same. |
| asm := fmt.Sprintf(" %s,%s", |
| gnuArg(&inst, 0, inst.Args[0], PC), |
| gnuArg(&inst, 1, inst.Args[1], PC)) |
| buf.WriteString(asm) |
| startArg = 3 |
| } |
| |
| case "paste.": |
| buf.WriteString(opName) |
| l := inst.Args[2].(Imm) |
| if l == 1 { |
| // L == 1 is an extended mnemonic for the same. |
| asm := fmt.Sprintf(" %s,%s", |
| gnuArg(&inst, 0, inst.Args[0], PC), |
| gnuArg(&inst, 1, inst.Args[1], PC)) |
| buf.WriteString(asm) |
| startArg = 3 |
| } |
| |
| case "mtfsf", "mtfsf.": |
| buf.WriteString(opName) |
| l := inst.Args[3].(Imm) |
| if l == 0 { |
| // L == 0 is an extended mnemonic for the same. |
| asm := fmt.Sprintf(" %s,%s,%s", |
| gnuArg(&inst, 0, inst.Args[0], PC), |
| gnuArg(&inst, 1, inst.Args[1], PC), |
| gnuArg(&inst, 2, inst.Args[2], PC)) |
| buf.WriteString(asm) |
| startArg = 4 |
| } |
| |
| case "sync": |
| lsc := inst.Args[0].(Imm)<<4 | inst.Args[1].(Imm) |
| switch lsc { |
| case 0x00: |
| buf.WriteString("hwsync") |
| startArg = 2 |
| case 0x10: |
| buf.WriteString("lwsync") |
| startArg = 2 |
| default: |
| buf.WriteString(opName) |
| } |
| |
| case "lbarx", "lharx", "lwarx", "ldarx": |
| // If EH == 0, omit printing EH. |
| eh := inst.Args[3].(Imm) |
| if eh == 0 { |
| argList = inst.Args[:3] |
| } |
| buf.WriteString(inst.Op.String()) |
| |
| case "paddi": |
| // There are several extended mnemonics. Notably, "pla" is |
| // the only valid mnemonic for paddi (R=1), In this case, RA must |
| // always be 0. Otherwise it is invalid. |
| r := inst.Args[3].(Imm) |
| ra := inst.Args[1].(Reg) |
| str := opName |
| if ra == R0 { |
| name := []string{"pli", "pla"} |
| str = fmt.Sprintf("%s %s,%s", |
| name[r&1], |
| gnuArg(&inst, 0, inst.Args[0], PC), |
| gnuArg(&inst, 2, inst.Args[2], PC)) |
| startArg = 4 |
| } else { |
| str = fmt.Sprintf("%s %s,%s,%s", opName, |
| gnuArg(&inst, 0, inst.Args[0], PC), |
| gnuArg(&inst, 1, inst.Args[1], PC), |
| gnuArg(&inst, 2, inst.Args[2], PC)) |
| startArg = 4 |
| if r == 1 { |
| // This is an illegal encoding (ra != 0 && r == 1) on ISA 3.1. |
| v := uint64(inst.Enc)<<32 | uint64(inst.SuffixEnc) |
| return fmt.Sprintf(".quad 0x%x", v) |
| } |
| } |
| buf.WriteString(str) |
| |
| default: |
| // Prefixed load/stores do not print the displacement register when R==1 (they are PCrel). |
| // This also implies RA should be 0. Likewise, when R==0, printing of R can be omitted. |
| if strings.HasPrefix(opName, "pl") || strings.HasPrefix(opName, "pst") { |
| r := inst.Args[3].(Imm) |
| ra := inst.Args[2].(Reg) |
| d := inst.Args[1].(Offset) |
| if r == 1 && ra == R0 { |
| str := fmt.Sprintf("%s %s,%d", opName, gnuArg(&inst, 0, inst.Args[0], PC), d) |
| buf.WriteString(str) |
| startArg = 4 |
| } else { |
| str := fmt.Sprintf("%s %s,%d(%s)", opName, |
| gnuArg(&inst, 0, inst.Args[0], PC), |
| d, |
| gnuArg(&inst, 2, inst.Args[2], PC)) |
| if r == 1 { |
| // This is an invalid encoding (ra != 0 && r == 1) on ISA 3.1. |
| v := uint64(inst.Enc)<<32 | uint64(inst.SuffixEnc) |
| return fmt.Sprintf(".quad 0x%x", v) |
| } |
| buf.WriteString(str) |
| startArg = 4 |
| } |
| } else { |
| buf.WriteString(opName) |
| } |
| } |
| for i, arg := range argList { |
| if arg == nil { |
| break |
| } |
| if i < startArg { |
| continue |
| } |
| text := gnuArg(&inst, i, arg, PC) |
| if text == "" { |
| continue |
| } |
| buf.WriteString(sep) |
| sep = "," |
| buf.WriteString(text) |
| } |
| return buf.String() |
| } |
| |
| // gnuArg formats arg (which is the argIndex's arg in inst) according to GNU rules. |
| // NOTE: because GNUSyntax is the only caller of this func, and it receives a copy |
| // of inst, it's ok to modify inst.Args here. |
| func gnuArg(inst *Inst, argIndex int, arg Arg, pc uint64) string { |
| // special cases for load/store instructions |
| if _, ok := arg.(Offset); ok { |
| if argIndex+1 == len(inst.Args) || inst.Args[argIndex+1] == nil { |
| panic(fmt.Errorf("wrong table: offset not followed by register")) |
| } |
| } |
| switch arg := arg.(type) { |
| case Reg: |
| if isLoadStoreOp(inst.Op) && argIndex == 1 && arg == R0 { |
| return "0" |
| } |
| return arg.String() |
| case CondReg: |
| // The CondReg can either be found in a CMP, where the |
| // condition register field is being set, or in an instruction |
| // like a branch or isel that is testing a bit in a condition |
| // register field. |
| if arg == CR0 && strings.HasPrefix(inst.Op.String(), "cmp") { |
| return "" // don't show cr0 for cmp instructions |
| } else if arg >= CR0 { |
| return fmt.Sprintf("cr%d", int(arg-CR0)) |
| } |
| bit := condBit[(arg-Cond0LT)%4] |
| if arg <= Cond0SO { |
| return bit |
| } |
| return fmt.Sprintf("4*cr%d+%s", int(arg-Cond0LT)/4, bit) |
| case Imm: |
| return fmt.Sprintf("%d", arg) |
| case SpReg: |
| switch int(arg) { |
| case 1: |
| return "xer" |
| case 8: |
| return "lr" |
| case 9: |
| return "ctr" |
| case 268: |
| return "tb" |
| default: |
| return fmt.Sprintf("%d", int(arg)) |
| } |
| case PCRel: |
| // If the arg is 0, use the relative address format. |
| // Otherwise the pc is meaningful, use absolute address. |
| if int(arg) == 0 { |
| return fmt.Sprintf(".%+#x", int(arg)) |
| } |
| addr := pc + uint64(int64(arg)) |
| return fmt.Sprintf("%#x", addr) |
| case Label: |
| return fmt.Sprintf("%#x", uint32(arg)) |
| case Offset: |
| reg := inst.Args[argIndex+1].(Reg) |
| removeArg(inst, argIndex+1) |
| if reg == R0 { |
| return fmt.Sprintf("%d(0)", int(arg)) |
| } |
| return fmt.Sprintf("%d(r%d)", int(arg), reg-R0) |
| } |
| return fmt.Sprintf("???(%v)", arg) |
| } |
| |
| // removeArg removes the arg in inst.Args[index]. |
| func removeArg(inst *Inst, index int) { |
| for i := index; i < len(inst.Args); i++ { |
| if i+1 < len(inst.Args) { |
| inst.Args[i] = inst.Args[i+1] |
| } else { |
| inst.Args[i] = nil |
| } |
| } |
| } |
| |
| // isLoadStoreOp returns true if op is a load or store instruction |
| func isLoadStoreOp(op Op) bool { |
| switch op { |
| case LBZ, LBZU, LBZX, LBZUX: |
| return true |
| case LHZ, LHZU, LHZX, LHZUX: |
| return true |
| case LHA, LHAU, LHAX, LHAUX: |
| return true |
| case LWZ, LWZU, LWZX, LWZUX: |
| return true |
| case LWA, LWAX, LWAUX: |
| return true |
| case LD, LDU, LDX, LDUX: |
| return true |
| case LQ: |
| return true |
| case STB, STBU, STBX, STBUX: |
| return true |
| case STH, STHU, STHX, STHUX: |
| return true |
| case STW, STWU, STWX, STWUX: |
| return true |
| case STD, STDU, STDX, STDUX: |
| return true |
| case STQ: |
| return true |
| case LHBRX, LWBRX, STHBRX, STWBRX: |
| return true |
| case LBARX, LWARX, LHARX, LDARX: |
| return true |
| } |
| return false |
| } |