mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
This change makes it easier to express instructions
with arbitrary number of operands.
Rationale: previous approach with operand "hiding" does
not scale well, AVX and especially AVX512 have many
instructions with 3+ operands.
x86 asm backend is updated to handle up to 6 explicit operands.
It also fixes issue with 4-th immediate operand type checks.
All `ytab` tables are updated accordingly.
Changes to non-x86 backends only include these patterns:
`p.From3 = X` => `p.SetFrom3(X)`
`p.From3.X = Y` => `p.GetFrom3().X = Y`
Over time, other backends can adapt Prog.RestArgs
and reduce the amount of workarounds.
-- Performance --
x/benchmark/build:
$ benchstat upstream.bench patched.bench
name old time/op new time/op delta
Build-48 21.7s ± 2% 21.8s ± 2% ~ (p=0.218 n=10+10)
name old binary-size new binary-size delta
Build-48 10.3M ± 0% 10.3M ± 0% ~ (all equal)
name old build-time/op new build-time/op delta
Build-48 21.7s ± 2% 21.8s ± 2% ~ (p=0.218 n=10+10)
name old build-peak-RSS-bytes new build-peak-RSS-bytes delta
Build-48 145MB ± 5% 148MB ± 5% ~ (p=0.218 n=10+10)
name old build-user+sys-time/op new build-user+sys-time/op delta
Build-48 21.0s ± 2% 21.2s ± 2% ~ (p=0.075 n=10+10)
Microbenchmark shows a slight slowdown.
name old time/op new time/op delta
AMD64asm-4 49.5ms ± 1% 49.9ms ± 1% +0.67% (p=0.001 n=23+15)
func BenchmarkAMD64asm(b *testing.B) {
for i := 0; i < b.N; i++ {
TestAMD64EndToEnd(nil)
TestAMD64Encoder(nil)
}
}
Change-Id: I4f1d37b5c2c966da3f2127705ccac9bff0038183
Reviewed-on: https://go-review.googlesource.com/63490
Run-TryBot: Iskander Sharipov <iskander.sharipov@intel.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
454 lines
9.1 KiB
Go
454 lines
9.1 KiB
Go
// Copyright 2015 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 obj
|
|
|
|
import (
|
|
"bytes"
|
|
"cmd/internal/objabi"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const REG_NONE = 0
|
|
|
|
func (p *Prog) Line() string {
|
|
return p.Ctxt.OutermostPos(p.Pos).Format(false)
|
|
}
|
|
|
|
var armCondCode = []string{
|
|
".EQ",
|
|
".NE",
|
|
".CS",
|
|
".CC",
|
|
".MI",
|
|
".PL",
|
|
".VS",
|
|
".VC",
|
|
".HI",
|
|
".LS",
|
|
".GE",
|
|
".LT",
|
|
".GT",
|
|
".LE",
|
|
"",
|
|
".NV",
|
|
}
|
|
|
|
/* ARM scond byte */
|
|
const (
|
|
C_SCOND = (1 << 4) - 1
|
|
C_SBIT = 1 << 4
|
|
C_PBIT = 1 << 5
|
|
C_WBIT = 1 << 6
|
|
C_FBIT = 1 << 7
|
|
C_UBIT = 1 << 7
|
|
C_SCOND_XOR = 14
|
|
)
|
|
|
|
// CConv formats ARM condition codes.
|
|
func CConv(s uint8) string {
|
|
if s == 0 {
|
|
return ""
|
|
}
|
|
sc := armCondCode[(s&C_SCOND)^C_SCOND_XOR]
|
|
if s&C_SBIT != 0 {
|
|
sc += ".S"
|
|
}
|
|
if s&C_PBIT != 0 {
|
|
sc += ".P"
|
|
}
|
|
if s&C_WBIT != 0 {
|
|
sc += ".W"
|
|
}
|
|
if s&C_UBIT != 0 { /* ambiguous with FBIT */
|
|
sc += ".U"
|
|
}
|
|
return sc
|
|
}
|
|
|
|
func (p *Prog) String() string {
|
|
if p == nil {
|
|
return "<nil Prog>"
|
|
}
|
|
|
|
if p.Ctxt == nil {
|
|
return "<Prog without ctxt>"
|
|
}
|
|
|
|
sc := CConv(p.Scond)
|
|
|
|
var buf bytes.Buffer
|
|
|
|
fmt.Fprintf(&buf, "%.5d (%v)\t%v%s", p.Pc, p.Line(), p.As, sc)
|
|
sep := "\t"
|
|
|
|
if p.From.Type != TYPE_NONE {
|
|
fmt.Fprintf(&buf, "%s%v", sep, Dconv(p, &p.From))
|
|
sep = ", "
|
|
}
|
|
if p.Reg != REG_NONE {
|
|
// Should not happen but might as well show it if it does.
|
|
fmt.Fprintf(&buf, "%s%v", sep, Rconv(int(p.Reg)))
|
|
sep = ", "
|
|
}
|
|
for i := range p.RestArgs {
|
|
fmt.Fprintf(&buf, "%s%v", sep, Dconv(p, &p.RestArgs[i]))
|
|
sep = ", "
|
|
}
|
|
|
|
if p.As == ATEXT {
|
|
// If there are attributes, print them. Otherwise, skip the comma.
|
|
// In short, print one of these two:
|
|
// TEXT foo(SB), DUPOK|NOSPLIT, $0
|
|
// TEXT foo(SB), $0
|
|
s := p.From.Sym.Attribute.TextAttrString()
|
|
if s != "" {
|
|
fmt.Fprintf(&buf, "%s%s", sep, s)
|
|
sep = ", "
|
|
}
|
|
}
|
|
if p.To.Type != TYPE_NONE {
|
|
fmt.Fprintf(&buf, "%s%v", sep, Dconv(p, &p.To))
|
|
}
|
|
if p.RegTo2 != REG_NONE {
|
|
fmt.Fprintf(&buf, "%s%v", sep, Rconv(int(p.RegTo2)))
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func (ctxt *Link) NewProg() *Prog {
|
|
p := new(Prog)
|
|
p.Ctxt = ctxt
|
|
return p
|
|
}
|
|
|
|
func (ctxt *Link) CanReuseProgs() bool {
|
|
return !ctxt.Debugasm
|
|
}
|
|
|
|
func (ctxt *Link) Dconv(a *Addr) string {
|
|
return Dconv(nil, a)
|
|
}
|
|
|
|
func Dconv(p *Prog, a *Addr) string {
|
|
var str string
|
|
|
|
switch a.Type {
|
|
default:
|
|
str = fmt.Sprintf("type=%d", a.Type)
|
|
|
|
case TYPE_NONE:
|
|
str = ""
|
|
if a.Name != NAME_NONE || a.Reg != 0 || a.Sym != nil {
|
|
str = fmt.Sprintf("%v(%v)(NONE)", Mconv(a), Rconv(int(a.Reg)))
|
|
}
|
|
|
|
case TYPE_REG:
|
|
// TODO(rsc): This special case is for x86 instructions like
|
|
// PINSRQ CX,$1,X6
|
|
// where the $1 is included in the p->to Addr.
|
|
// Move into a new field.
|
|
if a.Offset != 0 {
|
|
str = fmt.Sprintf("$%d,%v", a.Offset, Rconv(int(a.Reg)))
|
|
break
|
|
}
|
|
|
|
str = Rconv(int(a.Reg))
|
|
if a.Name != NAME_NONE || a.Sym != nil {
|
|
str = fmt.Sprintf("%v(%v)(REG)", Mconv(a), Rconv(int(a.Reg)))
|
|
}
|
|
|
|
case TYPE_BRANCH:
|
|
if a.Sym != nil {
|
|
str = fmt.Sprintf("%s(SB)", a.Sym.Name)
|
|
} else if p != nil && p.Pcond != nil {
|
|
str = fmt.Sprint(p.Pcond.Pc)
|
|
} else if a.Val != nil {
|
|
str = fmt.Sprint(a.Val.(*Prog).Pc)
|
|
} else {
|
|
str = fmt.Sprintf("%d(PC)", a.Offset)
|
|
}
|
|
|
|
case TYPE_INDIR:
|
|
str = fmt.Sprintf("*%s", Mconv(a))
|
|
|
|
case TYPE_MEM:
|
|
str = Mconv(a)
|
|
if a.Index != REG_NONE {
|
|
str += fmt.Sprintf("(%v*%d)", Rconv(int(a.Index)), int(a.Scale))
|
|
}
|
|
|
|
case TYPE_CONST:
|
|
if a.Reg != 0 {
|
|
str = fmt.Sprintf("$%v(%v)", Mconv(a), Rconv(int(a.Reg)))
|
|
} else {
|
|
str = fmt.Sprintf("$%v", Mconv(a))
|
|
}
|
|
|
|
case TYPE_TEXTSIZE:
|
|
if a.Val.(int32) == objabi.ArgsSizeUnknown {
|
|
str = fmt.Sprintf("$%d", a.Offset)
|
|
} else {
|
|
str = fmt.Sprintf("$%d-%d", a.Offset, a.Val.(int32))
|
|
}
|
|
|
|
case TYPE_FCONST:
|
|
str = fmt.Sprintf("%.17g", a.Val.(float64))
|
|
// Make sure 1 prints as 1.0
|
|
if !strings.ContainsAny(str, ".e") {
|
|
str += ".0"
|
|
}
|
|
str = fmt.Sprintf("$(%s)", str)
|
|
|
|
case TYPE_SCONST:
|
|
str = fmt.Sprintf("$%q", a.Val.(string))
|
|
|
|
case TYPE_ADDR:
|
|
str = fmt.Sprintf("$%s", Mconv(a))
|
|
|
|
case TYPE_SHIFT:
|
|
v := int(a.Offset)
|
|
ops := "<<>>->@>"
|
|
switch objabi.GOARCH {
|
|
case "arm":
|
|
op := ops[((v>>5)&3)<<1:]
|
|
if v&(1<<4) != 0 {
|
|
str = fmt.Sprintf("R%d%c%cR%d", v&15, op[0], op[1], (v>>8)&15)
|
|
} else {
|
|
str = fmt.Sprintf("R%d%c%c%d", v&15, op[0], op[1], (v>>7)&31)
|
|
}
|
|
if a.Reg != 0 {
|
|
str += fmt.Sprintf("(%v)", Rconv(int(a.Reg)))
|
|
}
|
|
case "arm64":
|
|
op := ops[((v>>22)&3)<<1:]
|
|
str = fmt.Sprintf("R%d%c%c%d", (v>>16)&31, op[0], op[1], (v>>10)&63)
|
|
default:
|
|
panic("TYPE_SHIFT is not supported on " + objabi.GOARCH)
|
|
}
|
|
|
|
case TYPE_REGREG:
|
|
str = fmt.Sprintf("(%v, %v)", Rconv(int(a.Reg)), Rconv(int(a.Offset)))
|
|
|
|
case TYPE_REGREG2:
|
|
str = fmt.Sprintf("%v, %v", Rconv(int(a.Offset)), Rconv(int(a.Reg)))
|
|
|
|
case TYPE_REGLIST:
|
|
str = regListConv(int(a.Offset))
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
func Mconv(a *Addr) string {
|
|
var str string
|
|
|
|
switch a.Name {
|
|
default:
|
|
str = fmt.Sprintf("name=%d", a.Name)
|
|
|
|
case NAME_NONE:
|
|
switch {
|
|
case a.Reg == REG_NONE:
|
|
str = fmt.Sprint(a.Offset)
|
|
case a.Offset == 0:
|
|
str = fmt.Sprintf("(%v)", Rconv(int(a.Reg)))
|
|
case a.Offset != 0:
|
|
str = fmt.Sprintf("%d(%v)", a.Offset, Rconv(int(a.Reg)))
|
|
}
|
|
|
|
// Note: a.Reg == REG_NONE encodes the default base register for the NAME_ type.
|
|
case NAME_EXTERN:
|
|
reg := "SB"
|
|
if a.Reg != REG_NONE {
|
|
reg = Rconv(int(a.Reg))
|
|
}
|
|
if a.Sym != nil {
|
|
str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
|
|
} else {
|
|
str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
|
|
}
|
|
|
|
case NAME_GOTREF:
|
|
reg := "SB"
|
|
if a.Reg != REG_NONE {
|
|
reg = Rconv(int(a.Reg))
|
|
}
|
|
if a.Sym != nil {
|
|
str = fmt.Sprintf("%s%s@GOT(%s)", a.Sym.Name, offConv(a.Offset), reg)
|
|
} else {
|
|
str = fmt.Sprintf("%s@GOT(%s)", offConv(a.Offset), reg)
|
|
}
|
|
|
|
case NAME_STATIC:
|
|
reg := "SB"
|
|
if a.Reg != REG_NONE {
|
|
reg = Rconv(int(a.Reg))
|
|
}
|
|
if a.Sym != nil {
|
|
str = fmt.Sprintf("%s<>%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
|
|
} else {
|
|
str = fmt.Sprintf("<>%s(%s)", offConv(a.Offset), reg)
|
|
}
|
|
|
|
case NAME_AUTO:
|
|
reg := "SP"
|
|
if a.Reg != REG_NONE {
|
|
reg = Rconv(int(a.Reg))
|
|
}
|
|
if a.Sym != nil {
|
|
str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
|
|
} else {
|
|
str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
|
|
}
|
|
|
|
case NAME_PARAM:
|
|
reg := "FP"
|
|
if a.Reg != REG_NONE {
|
|
reg = Rconv(int(a.Reg))
|
|
}
|
|
if a.Sym != nil {
|
|
str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
|
|
} else {
|
|
str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
|
|
}
|
|
}
|
|
return str
|
|
}
|
|
|
|
func offConv(off int64) string {
|
|
if off == 0 {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("%+d", off)
|
|
}
|
|
|
|
type regSet struct {
|
|
lo int
|
|
hi int
|
|
Rconv func(int) string
|
|
}
|
|
|
|
// Few enough architectures that a linear scan is fastest.
|
|
// Not even worth sorting.
|
|
var regSpace []regSet
|
|
|
|
/*
|
|
Each architecture defines a register space as a unique
|
|
integer range.
|
|
Here is the list of architectures and the base of their register spaces.
|
|
*/
|
|
|
|
const (
|
|
// Because of masking operations in the encodings, each register
|
|
// space should start at 0 modulo some power of 2.
|
|
RBase386 = 1 * 1024
|
|
RBaseAMD64 = 2 * 1024
|
|
RBaseARM = 3 * 1024
|
|
RBasePPC64 = 4 * 1024 // range [4k, 8k)
|
|
RBaseARM64 = 8 * 1024 // range [8k, 13k)
|
|
RBaseMIPS = 13 * 1024 // range [13k, 14k)
|
|
RBaseS390X = 14 * 1024 // range [14k, 15k)
|
|
)
|
|
|
|
// RegisterRegister binds a pretty-printer (Rconv) for register
|
|
// numbers to a given register number range. Lo is inclusive,
|
|
// hi exclusive (valid registers are lo through hi-1).
|
|
func RegisterRegister(lo, hi int, Rconv func(int) string) {
|
|
regSpace = append(regSpace, regSet{lo, hi, Rconv})
|
|
}
|
|
|
|
func Rconv(reg int) string {
|
|
if reg == REG_NONE {
|
|
return "NONE"
|
|
}
|
|
for i := range regSpace {
|
|
rs := ®Space[i]
|
|
if rs.lo <= reg && reg < rs.hi {
|
|
return rs.Rconv(reg)
|
|
}
|
|
}
|
|
return fmt.Sprintf("R???%d", reg)
|
|
}
|
|
|
|
func regListConv(list int) string {
|
|
str := ""
|
|
|
|
for i := 0; i < 16; i++ { // TODO: 16 is ARM-specific.
|
|
if list&(1<<uint(i)) != 0 {
|
|
if str == "" {
|
|
str += "["
|
|
} else {
|
|
str += ","
|
|
}
|
|
// This is ARM-specific; R10 is g.
|
|
if i == 10 {
|
|
str += "g"
|
|
} else {
|
|
str += fmt.Sprintf("R%d", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
str += "]"
|
|
return str
|
|
}
|
|
|
|
type opSet struct {
|
|
lo As
|
|
names []string
|
|
}
|
|
|
|
// Not even worth sorting
|
|
var aSpace []opSet
|
|
|
|
// RegisterOpcode binds a list of instruction names
|
|
// to a given instruction number range.
|
|
func RegisterOpcode(lo As, Anames []string) {
|
|
if len(Anames) > AllowedOpCodes {
|
|
panic(fmt.Sprintf("too many instructions, have %d max %d", len(Anames), AllowedOpCodes))
|
|
}
|
|
aSpace = append(aSpace, opSet{lo, Anames})
|
|
}
|
|
|
|
func (a As) String() string {
|
|
if 0 <= a && int(a) < len(Anames) {
|
|
return Anames[a]
|
|
}
|
|
for i := range aSpace {
|
|
as := &aSpace[i]
|
|
if as.lo <= a && int(a-as.lo) < len(as.names) {
|
|
return as.names[a-as.lo]
|
|
}
|
|
}
|
|
return fmt.Sprintf("A???%d", a)
|
|
}
|
|
|
|
var Anames = []string{
|
|
"XXX",
|
|
"CALL",
|
|
"DUFFCOPY",
|
|
"DUFFZERO",
|
|
"END",
|
|
"FUNCDATA",
|
|
"JMP",
|
|
"NOP",
|
|
"PCDATA",
|
|
"RET",
|
|
"TEXT",
|
|
"UNDEF",
|
|
}
|
|
|
|
func Bool2int(b bool) int {
|
|
// The compiler currently only optimizes this form.
|
|
// See issue 6011.
|
|
var i int
|
|
if b {
|
|
i = 1
|
|
} else {
|
|
i = 0
|
|
}
|
|
return i
|
|
}
|