| // 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" |
| "io" |
| "strings" |
| ) |
| |
| const REG_NONE = 0 |
| |
| // Line returns a string containing the filename and line number for p |
| func (p *Prog) Line() string { |
| return p.Ctxt.OutermostPos(p.Pos).Format(false, true) |
| } |
| func (p *Prog) InnermostLine(w io.Writer) { |
| p.Ctxt.InnermostPos(p.Pos).WriteTo(w, false, true) |
| } |
| |
| // InnermostLineNumber returns a string containing the line number for the |
| // innermost inlined function (if any inlining) at p's position |
| func (p *Prog) InnermostLineNumber() string { |
| return p.Ctxt.InnermostPos(p.Pos).LineNumber() |
| } |
| |
| // InnermostLineNumberHTML returns a string containing the line number for the |
| // innermost inlined function (if any inlining) at p's position |
| func (p *Prog) InnermostLineNumberHTML() string { |
| return p.Ctxt.InnermostPos(p.Pos).LineNumberHTML() |
| } |
| |
| // InnermostFilename returns a string containing the innermost |
| // (in inlining) filename at p's position |
| func (p *Prog) InnermostFilename() string { |
| // TODO For now, this is only used for debugging output, and if we need more/better information, it might change. |
| // An example of what we might want to see is the full stack of positions for inlined code, so we get some visibility into what is recorded there. |
| pos := p.Ctxt.InnermostPos(p.Pos) |
| if !pos.IsKnown() { |
| return "<unknown file name>" |
| } |
| return pos.Filename() |
| } |
| |
| 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 opcode suffix bits (Prog.Scond). |
| func CConv(s uint8) string { |
| if s == 0 { |
| return "" |
| } |
| for i := range opSuffixSpace { |
| sset := &opSuffixSpace[i] |
| if sset.arch == objabi.GOARCH { |
| return sset.cconv(s) |
| } |
| } |
| return fmt.Sprintf("SC???%d", s) |
| } |
| |
| // CConvARM formats ARM opcode suffix bits (mostly condition codes). |
| func CConvARM(s uint8) string { |
| // TODO: could be great to move suffix-related things into |
| // ARM asm backends some day. |
| // obj/x86 can be used as an example. |
| |
| 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>" |
| } |
| return fmt.Sprintf("%.5d (%v)\t%s", p.Pc, p.Line(), p.InstructionString()) |
| } |
| |
| func (p *Prog) InnermostString(w io.Writer) { |
| if p == nil { |
| io.WriteString(w, "<nil Prog>") |
| return |
| } |
| if p.Ctxt == nil { |
| io.WriteString(w, "<Prog without ctxt>") |
| return |
| } |
| fmt.Fprintf(w, "%.5d (", p.Pc) |
| p.InnermostLine(w) |
| io.WriteString(w, ")\t") |
| p.WriteInstructionString(w) |
| } |
| |
| // InstructionString returns a string representation of the instruction without preceding |
| // program counter or file and line number. |
| func (p *Prog) InstructionString() string { |
| buf := new(bytes.Buffer) |
| p.WriteInstructionString(buf) |
| return buf.String() |
| } |
| |
| // WriteInstructionString writes a string representation of the instruction without preceding |
| // program counter or file and line number. |
| func (p *Prog) WriteInstructionString(w io.Writer) { |
| if p == nil { |
| io.WriteString(w, "<nil Prog>") |
| return |
| } |
| |
| if p.Ctxt == nil { |
| io.WriteString(w, "<Prog without ctxt>") |
| return |
| } |
| |
| sc := CConv(p.Scond) |
| |
| io.WriteString(w, p.As.String()) |
| io.WriteString(w, sc) |
| sep := "\t" |
| |
| if p.From.Type != TYPE_NONE { |
| io.WriteString(w, sep) |
| WriteDconv(w, p, &p.From) |
| sep = ", " |
| } |
| if p.Reg != REG_NONE { |
| // Should not happen but might as well show it if it does. |
| fmt.Fprintf(w, "%s%v", sep, Rconv(int(p.Reg))) |
| sep = ", " |
| } |
| for i := range p.RestArgs { |
| io.WriteString(w, sep) |
| WriteDconv(w, 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(w, "%s%s", sep, s) |
| sep = ", " |
| } |
| } |
| if p.To.Type != TYPE_NONE { |
| io.WriteString(w, sep) |
| WriteDconv(w, p, &p.To) |
| } |
| if p.RegTo2 != REG_NONE { |
| fmt.Fprintf(w, "%s%v", sep, Rconv(int(p.RegTo2))) |
| } |
| } |
| |
| func (ctxt *Link) NewProg() *Prog { |
| p := new(Prog) |
| p.Ctxt = ctxt |
| return p |
| } |
| |
| func (ctxt *Link) CanReuseProgs() bool { |
| return ctxt.Debugasm == 0 |
| } |
| |
| func Dconv(p *Prog, a *Addr) string { |
| buf := new(bytes.Buffer) |
| WriteDconv(buf, p, a) |
| return buf.String() |
| } |
| |
| func WriteDconv(w io.Writer, p *Prog, a *Addr) { |
| switch a.Type { |
| default: |
| fmt.Fprintf(w, "type=%d", a.Type) |
| |
| case TYPE_NONE: |
| if a.Name != NAME_NONE || a.Reg != 0 || a.Sym != nil { |
| a.WriteNameTo(w) |
| fmt.Fprintf(w, "(%v)(NONE)", 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 && (a.Reg < RBaseARM64 || a.Reg >= RBaseMIPS) { |
| fmt.Fprintf(w, "$%d,%v", a.Offset, Rconv(int(a.Reg))) |
| return |
| } |
| |
| if a.Name != NAME_NONE || a.Sym != nil { |
| a.WriteNameTo(w) |
| fmt.Fprintf(w, "(%v)(REG)", Rconv(int(a.Reg))) |
| } else { |
| io.WriteString(w, Rconv(int(a.Reg))) |
| } |
| if (RBaseARM64+1<<10+1<<9) /* arm64.REG_ELEM */ <= a.Reg && |
| a.Reg < (RBaseARM64+1<<11) /* arm64.REG_ELEM_END */ { |
| fmt.Fprintf(w, "[%d]", a.Index) |
| } |
| |
| case TYPE_BRANCH: |
| if a.Sym != nil { |
| fmt.Fprintf(w, "%s(SB)", a.Sym.Name) |
| } else if p != nil && p.Pcond != nil { |
| fmt.Fprint(w, p.Pcond.Pc) |
| } else if a.Val != nil { |
| fmt.Fprint(w, a.Val.(*Prog).Pc) |
| } else { |
| fmt.Fprintf(w, "%d(PC)", a.Offset) |
| } |
| |
| case TYPE_INDIR: |
| io.WriteString(w, "*") |
| a.WriteNameTo(w) |
| |
| case TYPE_MEM: |
| a.WriteNameTo(w) |
| if a.Index != REG_NONE { |
| if a.Scale == 0 { |
| // arm64 shifted or extended register offset, scale = 0. |
| fmt.Fprintf(w, "(%v)", Rconv(int(a.Index))) |
| } else { |
| fmt.Fprintf(w, "(%v*%d)", Rconv(int(a.Index)), int(a.Scale)) |
| } |
| } |
| |
| case TYPE_CONST: |
| io.WriteString(w, "$") |
| a.WriteNameTo(w) |
| if a.Reg != 0 { |
| fmt.Fprintf(w, "(%v)", Rconv(int(a.Reg))) |
| } |
| |
| case TYPE_TEXTSIZE: |
| if a.Val.(int32) == objabi.ArgsSizeUnknown { |
| fmt.Fprintf(w, "$%d", a.Offset) |
| } else { |
| fmt.Fprintf(w, "$%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" |
| } |
| fmt.Fprintf(w, "$(%s)", str) |
| |
| case TYPE_SCONST: |
| fmt.Fprintf(w, "$%q", a.Val.(string)) |
| |
| case TYPE_ADDR: |
| io.WriteString(w, "$") |
| a.WriteNameTo(w) |
| |
| case TYPE_SHIFT: |
| v := int(a.Offset) |
| ops := "<<>>->@>" |
| switch objabi.GOARCH { |
| case "arm": |
| op := ops[((v>>5)&3)<<1:] |
| if v&(1<<4) != 0 { |
| fmt.Fprintf(w, "R%d%c%cR%d", v&15, op[0], op[1], (v>>8)&15) |
| } else { |
| fmt.Fprintf(w, "R%d%c%c%d", v&15, op[0], op[1], (v>>7)&31) |
| } |
| if a.Reg != 0 { |
| fmt.Fprintf(w, "(%v)", Rconv(int(a.Reg))) |
| } |
| case "arm64": |
| op := ops[((v>>22)&3)<<1:] |
| r := (v >> 16) & 31 |
| fmt.Fprintf(w, "%s%c%c%d", Rconv(r+RBaseARM64), op[0], op[1], (v>>10)&63) |
| default: |
| panic("TYPE_SHIFT is not supported on " + objabi.GOARCH) |
| } |
| |
| case TYPE_REGREG: |
| fmt.Fprintf(w, "(%v, %v)", Rconv(int(a.Reg)), Rconv(int(a.Offset))) |
| |
| case TYPE_REGREG2: |
| fmt.Fprintf(w, "%v, %v", Rconv(int(a.Offset)), Rconv(int(a.Reg))) |
| |
| case TYPE_REGLIST: |
| io.WriteString(w, RLconv(a.Offset)) |
| } |
| } |
| |
| func (a *Addr) WriteNameTo(w io.Writer) { |
| switch a.Name { |
| default: |
| fmt.Fprintf(w, "name=%d", a.Name) |
| |
| case NAME_NONE: |
| switch { |
| case a.Reg == REG_NONE: |
| fmt.Fprint(w, a.Offset) |
| case a.Offset == 0: |
| fmt.Fprintf(w, "(%v)", Rconv(int(a.Reg))) |
| case a.Offset != 0: |
| fmt.Fprintf(w, "%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 { |
| fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg) |
| } else { |
| fmt.Fprintf(w, "%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 { |
| fmt.Fprintf(w, "%s%s@GOT(%s)", a.Sym.Name, offConv(a.Offset), reg) |
| } else { |
| fmt.Fprintf(w, "%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 { |
| fmt.Fprintf(w, "%s<>%s(%s)", a.Sym.Name, offConv(a.Offset), reg) |
| } else { |
| fmt.Fprintf(w, "<>%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 { |
| fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg) |
| } else { |
| fmt.Fprintf(w, "%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 { |
| fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg) |
| } else { |
| fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg) |
| } |
| case NAME_TOCREF: |
| reg := "SB" |
| if a.Reg != REG_NONE { |
| reg = Rconv(int(a.Reg)) |
| } |
| if a.Sym != nil { |
| fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg) |
| } else { |
| fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg) |
| } |
| } |
| } |
| |
| func offConv(off int64) string { |
| if off == 0 { |
| return "" |
| } |
| return fmt.Sprintf("%+d", off) |
| } |
| |
| // opSuffixSet is like regListSet, but for opcode suffixes. |
| // |
| // Unlike some other similar structures, uint8 space is not |
| // divided by its own values set (because there are only 256 of them). |
| // Instead, every arch may interpret/format all 8 bits as they like, |
| // as long as they register proper cconv function for it. |
| type opSuffixSet struct { |
| arch string |
| cconv func(suffix uint8) string |
| } |
| |
| var opSuffixSpace []opSuffixSet |
| |
| // RegisterOpSuffix assigns cconv function for formatting opcode suffixes |
| // when compiling for GOARCH=arch. |
| // |
| // cconv is never called with 0 argument. |
| func RegisterOpSuffix(arch string, cconv func(uint8) string) { |
| opSuffixSpace = append(opSuffixSpace, opSuffixSet{ |
| arch: arch, |
| cconv: cconv, |
| }) |
| } |
| |
| 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) |
| RBaseRISCV = 15 * 1024 // range [15k, 16k) |
| RBaseWasm = 16 * 1024 |
| ) |
| |
| // 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) |
| } |
| |
| type regListSet struct { |
| lo int64 |
| hi int64 |
| RLconv func(int64) string |
| } |
| |
| var regListSpace []regListSet |
| |
| // Each architecture is allotted a distinct subspace: [Lo, Hi) for declaring its |
| // arch-specific register list numbers. |
| const ( |
| RegListARMLo = 0 |
| RegListARMHi = 1 << 16 |
| |
| // arm64 uses the 60th bit to differentiate from other archs |
| RegListARM64Lo = 1 << 60 |
| RegListARM64Hi = 1<<61 - 1 |
| |
| // x86 uses the 61th bit to differentiate from other archs |
| RegListX86Lo = 1 << 61 |
| RegListX86Hi = 1<<62 - 1 |
| ) |
| |
| // RegisterRegisterList binds a pretty-printer (RLconv) for register list |
| // numbers to a given register list number range. Lo is inclusive, |
| // hi exclusive (valid register list are lo through hi-1). |
| func RegisterRegisterList(lo, hi int64, rlconv func(int64) string) { |
| regListSpace = append(regListSpace, regListSet{lo, hi, rlconv}) |
| } |
| |
| func RLconv(list int64) string { |
| for i := range regListSpace { |
| rls := ®ListSpace[i] |
| if rls.lo <= list && list < rls.hi { |
| return rls.RLconv(list) |
| } |
| } |
| return fmt.Sprintf("RL???%d", list) |
| } |
| |
| 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", |
| "PCALIGN", |
| "PCDATA", |
| "RET", |
| "GETCALLERPC", |
| "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 |
| } |