]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: speed up compiling with -S
authorJosh Bleecher Snyder <josharian@gmail.com>
Sun, 26 Jan 2020 17:59:25 +0000 (09:59 -0800)
committerJosh Bleecher Snyder <josharian@gmail.com>
Mon, 20 Apr 2020 00:23:45 +0000 (00:23 +0000)
Compiling with -S was not implemented with performance in mind.
It allocates profligately. Compiling with -S is ~58% slower,
allocates ~47% more memory, and does ~183% more allocations.

compilecmp now uses -S to do finer-grained comparisons between
compiler versions, so I now care about its performance.

This change picks some of the lowest hanging fruit,
mostly by modifying printing routines to print directly to a writer,
rather than constructing a string first.

I have confirmed that compiling std+cmd with "-gcflags=all=-S -p=1"
and CGO_ENABLED=0 yields identical results before/after this change.
(-p=1 makes package compilation order deterministic. CGO_ENABLED=0
prevents cgo temp workdirs from showing up in filenames.)

Using the -S flag, the compiler performance impact is:

name        old time/op       new time/op       delta
Template          344ms ± 2%        301ms ± 2%  -12.45%  (p=0.000 n=22+24)
Unicode           136ms ± 3%        121ms ± 3%  -11.40%  (p=0.000 n=24+25)
GoTypes           1.24s ± 5%        1.09s ± 3%  -12.58%  (p=0.000 n=25+25)
Compiler          5.66s ± 4%        5.06s ± 2%  -10.56%  (p=0.000 n=25+20)
SSA               19.9s ± 3%        17.2s ± 4%  -13.64%  (p=0.000 n=25+25)
Flate             212ms ± 2%        188ms ± 2%  -11.33%  (p=0.000 n=25+24)
GoParser          278ms ± 3%        242ms ± 1%  -12.84%  (p=0.000 n=23+24)
Reflect           743ms ± 3%        657ms ± 5%  -11.56%  (p=0.000 n=24+25)
Tar               295ms ± 2%        263ms ± 2%  -10.78%  (p=0.000 n=25+25)
XML               409ms ± 2%        360ms ± 3%  -12.03%  (p=0.000 n=24+25)
[Geo mean]        714ms             629ms       -11.92%

name        old user-time/op  new user-time/op  delta
Template          430ms ± 5%        388ms ± 3%   -9.76%  (p=0.000 n=21+24)
Unicode           202ms ±12%        171ms ± 5%  -15.21%  (p=0.000 n=25+23)
GoTypes           1.58s ± 3%        1.42s ± 3%   -9.58%  (p=0.000 n=24+24)
Compiler          7.42s ± 3%        6.68s ± 8%   -9.93%  (p=0.000 n=25+25)
SSA               26.9s ± 3%        22.9s ± 3%  -14.85%  (p=0.000 n=25+25)
Flate             260ms ± 6%        234ms ± 3%   -9.69%  (p=0.000 n=23+25)
GoParser          354ms ± 1%        296ms ± 3%  -16.46%  (p=0.000 n=23+25)
Reflect           953ms ± 2%        865ms ± 4%   -9.14%  (p=0.000 n=24+24)
Tar               380ms ± 2%        348ms ± 2%   -8.28%  (p=0.000 n=25+22)
XML               530ms ± 3%        451ms ± 3%  -15.01%  (p=0.000 n=24+23)
[Geo mean]        929ms             819ms       -11.84%

name        old alloc/op      new alloc/op      delta
Template         54.1MB ± 0%       44.3MB ± 0%  -18.24%  (p=0.000 n=24+24)
Unicode          33.5MB ± 0%       30.6MB ± 0%   -8.57%  (p=0.000 n=25+25)
GoTypes           189MB ± 0%        152MB ± 0%  -19.55%  (p=0.000 n=25+23)
Compiler          875MB ± 0%        703MB ± 0%  -19.70%  (p=0.000 n=25+25)
SSA              3.19GB ± 0%       2.51GB ± 0%  -21.50%  (p=0.000 n=25+25)
Flate            32.9MB ± 0%       27.3MB ± 0%  -17.04%  (p=0.000 n=25+25)
GoParser         43.9MB ± 0%       35.1MB ± 0%  -20.19%  (p=0.000 n=25+25)
Reflect           117MB ± 0%         96MB ± 0%  -18.22%  (p=0.000 n=24+23)
Tar              48.6MB ± 0%       40.6MB ± 0%  -16.39%  (p=0.000 n=25+24)
XML              65.7MB ± 0%       53.9MB ± 0%  -17.93%  (p=0.000 n=25+23)
[Geo mean]        118MB              97MB       -17.80%

name        old allocs/op     new allocs/op     delta
Template          1.07M ± 0%        0.60M ± 0%  -43.90%  (p=0.000 n=25+24)
Unicode            539k ± 0%         398k ± 0%  -26.20%  (p=0.000 n=23+25)
GoTypes           3.97M ± 0%        2.19M ± 0%  -44.90%  (p=0.000 n=25+24)
Compiler          17.6M ± 0%         9.5M ± 0%  -46.39%  (p=0.000 n=22+23)
SSA               66.1M ± 0%        34.1M ± 0%  -48.41%  (p=0.000 n=25+22)
Flate              629k ± 0%         365k ± 0%  -41.95%  (p=0.000 n=25+25)
GoParser           929k ± 0%         500k ± 0%  -46.11%  (p=0.000 n=25+25)
Reflect           2.49M ± 0%        1.47M ± 0%  -41.00%  (p=0.000 n=24+25)
Tar                919k ± 0%         534k ± 0%  -41.94%  (p=0.000 n=25+24)
XML               1.28M ± 0%        0.71M ± 0%  -44.72%  (p=0.000 n=25+24)
[Geo mean]        2.32M             1.33M       -42.82%

This change also speeds up cmd/objdump a modest amount, ~4%.

Change-Id: I7c7aa2b365688bc44b3ef6e1d03bcf934699cabc
Reviewed-on: https://go-review.googlesource.com/c/go/+/216857
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/cmd/internal/obj/objfile.go
src/cmd/internal/obj/util.go
src/cmd/internal/src/pos.go
src/cmd/internal/src/pos_test.go

index cb6b7090669639b2c47c7e41c923960c132756c5..2b0c45d6b2d5bf2d3f8d1f18764c2456d3993aa8 100644 (file)
@@ -13,6 +13,7 @@ import (
        "cmd/internal/objabi"
        "cmd/internal/sys"
        "fmt"
+       "io"
        "log"
        "path/filepath"
        "sort"
@@ -262,13 +263,13 @@ func (ctxt *Link) writeSymDebugNamed(s *LSym, name string) {
        fmt.Fprintf(ctxt.Bso, "\n")
        if s.Type == objabi.STEXT {
                for p := s.Func.Text; p != nil; p = p.Link {
-                       var s string
+                       fmt.Fprintf(ctxt.Bso, "\t%#04x ", uint(int(p.Pc)))
                        if ctxt.Debugasm > 1 {
-                               s = p.String()
+                               io.WriteString(ctxt.Bso, p.String())
                        } else {
-                               s = p.InnermostString()
+                               p.InnermostString(ctxt.Bso)
                        }
-                       fmt.Fprintf(ctxt.Bso, "\t%#04x %s\n", uint(int(p.Pc)), s)
+                       fmt.Fprintln(ctxt.Bso)
                }
        }
        for i := 0; i < len(s.P); i += 16 {
@@ -283,11 +284,11 @@ func (ctxt *Link) writeSymDebugNamed(s *LSym, name string) {
                fmt.Fprintf(ctxt.Bso, "  ")
                for j = i; j < i+16 && j < len(s.P); j++ {
                        c := int(s.P[j])
+                       b := byte('.')
                        if ' ' <= c && c <= 0x7e {
-                               fmt.Fprintf(ctxt.Bso, "%c", c)
-                       } else {
-                               fmt.Fprintf(ctxt.Bso, ".")
+                               b = byte(c)
                        }
+                       ctxt.Bso.WriteByte(b)
                }
 
                fmt.Fprintf(ctxt.Bso, "\n")
index 46d662c6c811d36dde35d3ec101c03e38ce70be6..d0200264452cd90468687c2efce0e41ceb4dfd61 100644 (file)
@@ -8,6 +8,7 @@ import (
        "bytes"
        "cmd/internal/objabi"
        "fmt"
+       "io"
        "strings"
 )
 
@@ -17,8 +18,8 @@ const REG_NONE = 0
 func (p *Prog) Line() string {
        return p.Ctxt.OutermostPos(p.Pos).Format(false, true)
 }
-func (p *Prog) InnermostLine() string {
-       return p.Ctxt.InnermostPos(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
@@ -121,45 +122,61 @@ func (p *Prog) String() string {
        return fmt.Sprintf("%.5d (%v)\t%s", p.Pc, p.Line(), p.InstructionString())
 }
 
-func (p *Prog) InnermostString() string {
+func (p *Prog) InnermostString(w io.Writer) {
        if p == nil {
-               return "<nil Prog>"
+               io.WriteString(w, "<nil Prog>")
+               return
        }
        if p.Ctxt == nil {
-               return "<Prog without ctxt>"
+               io.WriteString(w, "<Prog without ctxt>")
+               return
        }
-       return fmt.Sprintf("%.5d (%v)\t%s", p.Pc, p.InnermostLine(), p.InstructionString())
+       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 {
-               return "<nil Prog>"
+               io.WriteString(w, "<nil Prog>")
+               return
        }
 
        if p.Ctxt == nil {
-               return "<Prog without ctxt>"
+               io.WriteString(w, "<Prog without ctxt>")
+               return
        }
 
        sc := CConv(p.Scond)
 
-       var buf bytes.Buffer
-
-       fmt.Fprintf(&buf, "%v%s", p.As, sc)
+       io.WriteString(w, p.As.String())
+       io.WriteString(w, sc)
        sep := "\t"
 
        if p.From.Type != TYPE_NONE {
-               fmt.Fprintf(&buf, "%s%v", sep, Dconv(p, &p.From))
+               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(&buf, "%s%v", sep, Rconv(int(p.Reg)))
+               fmt.Fprintf(w, "%s%v", sep, Rconv(int(p.Reg)))
                sep = ", "
        }
        for i := range p.RestArgs {
-               fmt.Fprintf(&buf, "%s%v", sep, Dconv(p, &p.RestArgs[i]))
+               io.WriteString(w, sep)
+               WriteDconv(w, p, &p.RestArgs[i])
                sep = ", "
        }
 
@@ -170,17 +187,17 @@ func (p *Prog) InstructionString() string {
                // TEXT foo(SB), $0
                s := p.From.Sym.Attribute.TextAttrString()
                if s != "" {
-                       fmt.Fprintf(&buf, "%s%s", sep, s)
+                       fmt.Fprintf(w, "%s%s", sep, s)
                        sep = ", "
                }
        }
        if p.To.Type != TYPE_NONE {
-               fmt.Fprintf(&buf, "%s%v", sep, Dconv(p, &p.To))
+               io.WriteString(w, sep)
+               WriteDconv(w, p, &p.To)
        }
        if p.RegTo2 != REG_NONE {
-               fmt.Fprintf(&buf, "%s%v", sep, Rconv(int(p.RegTo2)))
+               fmt.Fprintf(w, "%s%v", sep, Rconv(int(p.RegTo2)))
        }
-       return buf.String()
 }
 
 func (ctxt *Link) NewProg() *Prog {
@@ -194,16 +211,20 @@ func (ctxt *Link) CanReuseProgs() bool {
 }
 
 func Dconv(p *Prog, a *Addr) string {
-       var str 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:
-               str = fmt.Sprintf("type=%d", a.Type)
+               fmt.Fprintf(w, "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)))
+                       a.WriteNameTo(w)
+                       fmt.Fprintf(w, "(%v)(NONE)", Rconv(int(a.Reg)))
                }
 
        case TYPE_REG:
@@ -212,71 +233,75 @@ func Dconv(p *Prog, a *Addr) string {
                // 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) {
-                       str = fmt.Sprintf("$%d,%v", a.Offset, Rconv(int(a.Reg)))
-                       break
+                       fmt.Fprintf(w, "$%d,%v", a.Offset, Rconv(int(a.Reg)))
+                       return
                }
 
-               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)))
+                       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 */ {
-                       str += fmt.Sprintf("[%d]", a.Index)
+                       fmt.Fprintf(w, "[%d]", a.Index)
                }
 
        case TYPE_BRANCH:
                if a.Sym != nil {
-                       str = fmt.Sprintf("%s(SB)", a.Sym.Name)
+                       fmt.Fprintf(w, "%s(SB)", a.Sym.Name)
                } else if p != nil && p.Pcond != nil {
-                       str = fmt.Sprint(p.Pcond.Pc)
+                       fmt.Fprint(w, p.Pcond.Pc)
                } else if a.Val != nil {
-                       str = fmt.Sprint(a.Val.(*Prog).Pc)
+                       fmt.Fprint(w, a.Val.(*Prog).Pc)
                } else {
-                       str = fmt.Sprintf("%d(PC)", a.Offset)
+                       fmt.Fprintf(w, "%d(PC)", a.Offset)
                }
 
        case TYPE_INDIR:
-               str = fmt.Sprintf("*%s", Mconv(a))
+               io.WriteString(w, "*")
+               a.WriteNameTo(w)
 
        case TYPE_MEM:
-               str = Mconv(a)
+               a.WriteNameTo(w)
                if a.Index != REG_NONE {
                        if a.Scale == 0 {
                                // arm64 shifted or extended register offset, scale = 0.
-                               str += fmt.Sprintf("(%v)", Rconv(int(a.Index)))
+                               fmt.Fprintf(w, "(%v)", Rconv(int(a.Index)))
                        } else {
-                               str += fmt.Sprintf("(%v*%d)", Rconv(int(a.Index)), int(a.Scale))
+                               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 {
-                       str = fmt.Sprintf("$%v(%v)", Mconv(a), Rconv(int(a.Reg)))
-               } else {
-                       str = fmt.Sprintf("$%v", Mconv(a))
+                       fmt.Fprintf(w, "(%v)", Rconv(int(a.Reg)))
                }
 
        case TYPE_TEXTSIZE:
                if a.Val.(int32) == objabi.ArgsSizeUnknown {
-                       str = fmt.Sprintf("$%d", a.Offset)
+                       fmt.Fprintf(w, "$%d", a.Offset)
                } else {
-                       str = fmt.Sprintf("$%d-%d", a.Offset, a.Val.(int32))
+                       fmt.Fprintf(w, "$%d-%d", a.Offset, a.Val.(int32))
                }
 
        case TYPE_FCONST:
-               str = fmt.Sprintf("%.17g", a.Val.(float64))
+               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)
+               fmt.Fprintf(w, "$(%s)", str)
 
        case TYPE_SCONST:
-               str = fmt.Sprintf("$%q", a.Val.(string))
+               fmt.Fprintf(w, "$%q", a.Val.(string))
 
        case TYPE_ADDR:
-               str = fmt.Sprintf("$%s", Mconv(a))
+               io.WriteString(w, "$")
+               a.WriteNameTo(w)
 
        case TYPE_SHIFT:
                v := int(a.Offset)
@@ -285,49 +310,45 @@ func Dconv(p *Prog, a *Addr) string {
                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)
+                               fmt.Fprintf(w, "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)
+                               fmt.Fprintf(w, "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)))
+                               fmt.Fprintf(w, "(%v)", Rconv(int(a.Reg)))
                        }
                case "arm64":
                        op := ops[((v>>22)&3)<<1:]
                        r := (v >> 16) & 31
-                       str = fmt.Sprintf("%s%c%c%d", Rconv(r+RBaseARM64), op[0], op[1], (v>>10)&63)
+                       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:
-               str = fmt.Sprintf("(%v, %v)", Rconv(int(a.Reg)), Rconv(int(a.Offset)))
+               fmt.Fprintf(w, "(%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)))
+               fmt.Fprintf(w, "%v, %v", Rconv(int(a.Offset)), Rconv(int(a.Reg)))
 
        case TYPE_REGLIST:
-               str = RLconv(a.Offset)
+               io.WriteString(w, RLconv(a.Offset))
        }
-
-       return str
 }
 
-func Mconv(a *Addr) string {
-       var str string
-
+func (a *Addr) WriteNameTo(w io.Writer) {
        switch a.Name {
        default:
-               str = fmt.Sprintf("name=%d", a.Name)
+               fmt.Fprintf(w, "name=%d", a.Name)
 
        case NAME_NONE:
                switch {
                case a.Reg == REG_NONE:
-                       str = fmt.Sprint(a.Offset)
+                       fmt.Fprint(w, a.Offset)
                case a.Offset == 0:
-                       str = fmt.Sprintf("(%v)", Rconv(int(a.Reg)))
+                       fmt.Fprintf(w, "(%v)", Rconv(int(a.Reg)))
                case a.Offset != 0:
-                       str = fmt.Sprintf("%d(%v)", a.Offset, Rconv(int(a.Reg)))
+                       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.
@@ -337,9 +358,9 @@ func Mconv(a *Addr) string {
                        reg = Rconv(int(a.Reg))
                }
                if a.Sym != nil {
-                       str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
                } else {
-                       str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg)
                }
 
        case NAME_GOTREF:
@@ -348,9 +369,9 @@ func Mconv(a *Addr) string {
                        reg = Rconv(int(a.Reg))
                }
                if a.Sym != nil {
-                       str = fmt.Sprintf("%s%s@GOT(%s)", a.Sym.Name, offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s%s@GOT(%s)", a.Sym.Name, offConv(a.Offset), reg)
                } else {
-                       str = fmt.Sprintf("%s@GOT(%s)", offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s@GOT(%s)", offConv(a.Offset), reg)
                }
 
        case NAME_STATIC:
@@ -359,9 +380,9 @@ func Mconv(a *Addr) string {
                        reg = Rconv(int(a.Reg))
                }
                if a.Sym != nil {
-                       str = fmt.Sprintf("%s<>%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s<>%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
                } else {
-                       str = fmt.Sprintf("<>%s(%s)", offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "<>%s(%s)", offConv(a.Offset), reg)
                }
 
        case NAME_AUTO:
@@ -370,9 +391,9 @@ func Mconv(a *Addr) string {
                        reg = Rconv(int(a.Reg))
                }
                if a.Sym != nil {
-                       str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
                } else {
-                       str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg)
                }
 
        case NAME_PARAM:
@@ -381,9 +402,9 @@ func Mconv(a *Addr) string {
                        reg = Rconv(int(a.Reg))
                }
                if a.Sym != nil {
-                       str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
                } else {
-                       str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg)
                }
        case NAME_TOCREF:
                reg := "SB"
@@ -391,13 +412,11 @@ func Mconv(a *Addr) string {
                        reg = Rconv(int(a.Reg))
                }
                if a.Sym != nil {
-                       str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
                } else {
-                       str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
+                       fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg)
                }
-
        }
-       return str
 }
 
 func offConv(off int64) string {
index 861d9188b13d6ddce8e2ee3a81b2662fe921d0fd..b6816a56e0bfbf1ad3675ba60d98dceccf737677 100644 (file)
@@ -7,8 +7,9 @@
 package src
 
 import (
+       "bytes"
        "fmt"
-       "strconv"
+       "io"
 )
 
 // A Pos encodes a source position consisting of a (line, column) number pair
@@ -129,13 +130,22 @@ func (p Pos) String() string {
 // shown as well, as in "filename:line[origfile:origline:origcolumn] if
 // showOrig is set.
 func (p Pos) Format(showCol, showOrig bool) string {
+       buf := new(bytes.Buffer)
+       p.WriteTo(buf, showCol, showOrig)
+       return buf.String()
+}
+
+// WriteTo a position to w, formatted as Format does.
+func (p Pos) WriteTo(w io.Writer, showCol, showOrig bool) {
        if !p.IsKnown() {
-               return "<unknown line number>"
+               io.WriteString(w, "<unknown line number>")
+               return
        }
 
        if b := p.base; b == b.Pos().base {
                // base is file base (incl. nil)
-               return format(p.Filename(), p.Line(), p.Col(), showCol)
+               format(w, p.Filename(), p.Line(), p.Col(), showCol)
+               return
        }
 
        // base is relative
@@ -146,22 +156,32 @@ func (p Pos) Format(showCol, showOrig bool) string {
        // that's provided via a line directive).
        // TODO(gri) This may not be true if we have an inlining base.
        // We may want to differentiate at some point.
-       s := format(p.RelFilename(), p.RelLine(), p.RelCol(), showCol)
+       format(w, p.RelFilename(), p.RelLine(), p.RelCol(), showCol)
        if showOrig {
-               s += "[" + format(p.Filename(), p.Line(), p.Col(), showCol) + "]"
+               io.WriteString(w, "[")
+               format(w, p.Filename(), p.Line(), p.Col(), showCol)
+               io.WriteString(w, "]")
        }
-       return s
 }
 
 // format formats a (filename, line, col) tuple as "filename:line" (showCol
 // is false or col == 0) or "filename:line:column" (showCol is true and col != 0).
-func format(filename string, line, col uint, showCol bool) string {
-       s := filename + ":" + strconv.FormatUint(uint64(line), 10)
+func format(w io.Writer, filename string, line, col uint, showCol bool) {
+       io.WriteString(w, filename)
+       io.WriteString(w, ":")
+       fmt.Fprint(w, line)
        // col == 0 and col == colMax are interpreted as unknown column values
        if showCol && 0 < col && col < colMax {
-               s += ":" + strconv.FormatUint(uint64(col), 10)
+               io.WriteString(w, ":")
+               fmt.Fprint(w, col)
        }
-       return s
+}
+
+// formatstr wraps format to return a string.
+func formatstr(filename string, line, col uint, showCol bool) string {
+       buf := new(bytes.Buffer)
+       format(buf, filename, line, col, showCol)
+       return buf.String()
 }
 
 // ----------------------------------------------------------------------------
index d6131bab4c65bb8e8e3c9392e31404224462da85..d4cd0e7ff1b1e0eadc777deb44dbece6adf04225 100644 (file)
@@ -147,7 +147,7 @@ func TestLico(t *testing.T) {
                {makeLico(lineMax+1, colMax+1), fmt.Sprintf(":%d", lineMax), lineMax, 0},
        } {
                x := test.x
-               if got := format("", x.Line(), x.Col(), true); got != test.string {
+               if got := formatstr("", x.Line(), x.Col(), true); got != test.string {
                        t.Errorf("%s: got %q", test.string, got)
                }
        }
@@ -179,7 +179,7 @@ func TestIsStmt(t *testing.T) {
                {makeLico(lineMax+1, colMax+1).withNotStmt(), fmt.Sprintf(":%d", lineMax) + not, lineMax, 0},
        } {
                x := test.x
-               if got := format("", x.Line(), x.Col(), true) + fmt.Sprintf(":%d", x.IsStmt()); got != test.string {
+               if got := formatstr("", x.Line(), x.Col(), true) + fmt.Sprintf(":%d", x.IsStmt()); got != test.string {
                        t.Errorf("%s: got %q", test.string, got)
                }
        }
@@ -219,7 +219,7 @@ func TestLogue(t *testing.T) {
                {makeLico(lineMax, 1).withXlogue(PosEpilogueBegin), fmt.Sprintf(":%d:1", lineMax) + defs + epi, lineMax, 1},
        } {
                x := test.x
-               if got := format("", x.Line(), x.Col(), true) + fmt.Sprintf(":%d:%d", x.IsStmt(), x.Xlogue()); got != test.string {
+               if got := formatstr("", x.Line(), x.Col(), true) + fmt.Sprintf(":%d:%d", x.IsStmt(), x.Xlogue()); got != test.string {
                        t.Errorf("%d: %s: got %q", i, test.string, got)
                }
        }