472 lines
12 KiB
Go
472 lines
12 KiB
Go
// 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
|
|
}
|