ppc64/ppc64asm: improve disassembly for ppc64

The following improvements are included in this change:
- Display common special purpose registers for mtlr,mflr,mtctr,mfctr,
mtxer,mfxer,mftb; for others use mtspr and mfspr.
- Provide branch condition information (lt, gt, eq, ne, ge, le).
- Add cr number if cr1-cr7 is used.
- Pass pc to gnuArg to generate branch targets that are not relative.

Change-Id: Ia3ef6cb248c484a3ad72545e68d1ca59e32ae645
Reviewed-on: https://go-review.googlesource.com/c/arch/+/194597
Run-TryBot: Lynn Boger <laboger@linux.vnet.ibm.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
diff --git a/ppc64/ppc64asm/decode_test.go b/ppc64/ppc64asm/decode_test.go
index 039b3ed..7e7638c 100644
--- a/ppc64/ppc64asm/decode_test.go
+++ b/ppc64/ppc64asm/decode_test.go
@@ -49,7 +49,7 @@
 		} else {
 			switch syntax {
 			case "gnu":
-				out = GNUSyntax(inst)
+				out = GNUSyntax(inst, 0)
 			case "plan9":
 				out = GoSyntax(inst, 0, nil)
 			default:
diff --git a/ppc64/ppc64asm/ext_test.go b/ppc64/ppc64asm/ext_test.go
index cb7f319..f9242e1 100644
--- a/ppc64/ppc64asm/ext_test.go
+++ b/ppc64/ppc64asm/ext_test.go
@@ -245,7 +245,7 @@
 		//case "arm":
 		//	text = ARMSyntax(inst)
 		case "gnu":
-			text = GNUSyntax(inst)
+			text = GNUSyntax(inst, 0)
 		//case "plan9":
 		//	text = GoSyntax(inst, 0, nil)
 		default:
diff --git a/ppc64/ppc64asm/gnu.go b/ppc64/ppc64asm/gnu.go
index 70872bb..fc29164 100644
--- a/ppc64/ppc64asm/gnu.go
+++ b/ppc64/ppc64asm/gnu.go
@@ -10,9 +10,14 @@
 	"strings"
 )
 
+var (
+	condBit    = [4]string{"lt", "gt", "eq", "so"}
+	condBitNeg = [4]string{"ge", "le", "ne", "so"}
+)
+
 // 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) string {
+func GNUSyntax(inst Inst, pc uint64) string {
 	var buf bytes.Buffer
 	// When there are all 0s, identify them as the disassembler
 	// in binutils would.
@@ -21,13 +26,125 @@
 	} else if inst.Op == 0 {
 		return "error: unknown instruction"
 	}
-	buf.WriteString(inst.Op.String())
+
+	PC := pc
+	// Special handling for some ops
+	startArg := 0
 	sep := " "
+	switch inst.Op.String() {
+	case "bc":
+		bo := gnuArg(&inst, 0, inst.Args[0], PC)
+		bi := inst.Args[1]
+		switch bi := bi.(type) {
+		case CondReg:
+			if bi >= CR0 {
+				if bi == CR0 && bo == "16" {
+					buf.WriteString("bdnz")
+				}
+				buf.WriteString(fmt.Sprintf("bc cr%d", bi-CR0))
+			}
+			cr := bi / 4
+			switch bo {
+			case "4":
+				bit := condBitNeg[(bi-Cond0LT)%4]
+				if cr == 0 {
+					buf.WriteString(fmt.Sprintf("b%s", bit))
+				} else {
+					buf.WriteString(fmt.Sprintf("b%s cr%d,", bit, cr))
+					sep = ""
+				}
+			case "12":
+				bit := condBit[(bi-Cond0LT)%4]
+				if cr == 0 {
+					buf.WriteString(fmt.Sprintf("b%s", bit))
+				} else {
+					buf.WriteString(fmt.Sprintf("b%s cr%d,", bit, cr))
+					sep = ""
+				}
+			case "8":
+				bit := condBit[(bi-Cond0LT)%4]
+				sep = ""
+				if cr == 0 {
+					buf.WriteString(fmt.Sprintf("bdnzt %s,", bit))
+				} else {
+					buf.WriteString(fmt.Sprintf("bdnzt cr%d,%s,", cr, bit))
+				}
+			case "16":
+				if cr == 0 && bi == Cond0LT {
+					buf.WriteString("bdnz")
+				} else {
+					buf.WriteString(fmt.Sprintf("bdnz cr%d,", cr))
+					sep = ""
+				}
+			}
+			startArg = 2
+		default:
+			fmt.Printf("Unexpected bi: %d for bc with bo: %s\n", bi, bo)
+		}
+		startArg = 2
+	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")
+		}
+
+	default:
+		buf.WriteString(inst.Op.String())
+	}
 	for i, arg := range inst.Args[:] {
 		if arg == nil {
 			break
 		}
-		text := gnuArg(&inst, i, arg)
+		if i < startArg {
+			continue
+		}
+		text := gnuArg(&inst, i, arg, PC)
 		if text == "" {
 			continue
 		}
@@ -41,7 +158,7 @@
 // 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) string {
+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 {
@@ -55,22 +172,43 @@
 		}
 		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 := [4]string{"lt", "gt", "eq", "so"}[(arg-Cond0LT)%4]
+		bit := condBit[(arg-Cond0LT)%4]
 		if arg <= Cond0SO {
 			return bit
 		}
-		return fmt.Sprintf("4*cr%d+%s", int(arg-Cond0LT)/4, bit)
+		return fmt.Sprintf("%s cr%d", bit, int(arg-Cond0LT)/4)
 	case Imm:
 		return fmt.Sprintf("%d", arg)
 	case SpReg:
-		return fmt.Sprintf("%d", int(arg))
+		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:
-		return fmt.Sprintf(".%+#x", int(arg))
+		// 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:
diff --git a/ppc64/ppc64asm/testdata/decode.txt b/ppc64/ppc64asm/testdata/decode.txt
index 2a89de0..69e6942 100644
--- a/ppc64/ppc64asm/testdata/decode.txt
+++ b/ppc64/ppc64asm/testdata/decode.txt
@@ -9,7 +9,7 @@
 00002000|	gnu	error: unknown instruction
 a1841e80|	gnu	lhz r12,7808(r4)
 a1841e80|	plan9	MOVHZ 7808(R4),R12
-42093d10|	gnu	bc 16,4*cr2+gt,.+0x3d10
+42000004|	gnu	bdnz 0x4
 e38d5b90|	gnu	lq r28,23440(r13)
 84127a20|	gnu	lwzu r0,31264(r18)
 84127a20|	plan9	MOVWZU 31264(R18),R0
@@ -54,3 +54,23 @@
 7c941f19|	plan9	STXVW4X VS36,(R3)(R20)
 7c6520a8|	gnu	ldarx r3,r5,r4
 7c6520a8|	plan9	LDAR (R4)(R5),R3
+7c6803a6|	plan9	MOVD R3,LR
+7c6802a6|	plan9	MOVD LR,R3
+7c6803a6|	gnu	mtlr r3
+7c6802a6|	gnu	mflr r3
+7c6903a6|	plan9	MOVD R3,CTR
+7c6902a6|	plan9	MOVD CTR,R3
+7c6903a6|	gnu	mtctr r3
+7c6902a6|	gnu	mfctr r3
+7c6c42a6|	gnu	mftb r3
+7c6c42a6|	plan9	MOVD SPR(268),R3
+7c8202a6|	plan9	MOVD SPR(2),R4
+7c8202a6|	gnu	mfspr r4,2
+41820010|	plan9	BEQ 0x10
+41820010|	gnu	beq 0x10
+4086000c|	plan9	BNE CR1,0xc
+4086000c|	gnu	bne cr1,0xc
+41880008|	plan9	BLT CR2,0x8
+41880008|	gnu	blt cr2,0x8
+418d0004|	plan9	BGT CR3,0x4
+418d0004|	gnu	bgt cr3,0x4