diff options
| author | Josh Bleecher Snyder <josharian@gmail.com> | 2020-01-26 09:59:25 -0800 |
|---|---|---|
| committer | Josh Bleecher Snyder <josharian@gmail.com> | 2020-04-20 00:23:45 +0000 |
| commit | 1dcf34f2ecee7bfa5be0921c12d3661a03f259ae (patch) | |
| tree | cee16ab623a873eee94980ebbeed94f72e0ba0b7 /src/cmd | |
| parent | 04040ec9f9c763aee549d6afa3d4f0c34adf9cc1 (diff) | |
| download | go-1dcf34f2ecee7bfa5be0921c12d3661a03f259ae.tar.xz | |
cmd/compile: speed up compiling with -S
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>
Diffstat (limited to 'src/cmd')
| -rw-r--r-- | src/cmd/internal/obj/objfile.go | 15 | ||||
| -rw-r--r-- | src/cmd/internal/obj/util.go | 167 | ||||
| -rw-r--r-- | src/cmd/internal/src/pos.go | 40 | ||||
| -rw-r--r-- | src/cmd/internal/src/pos_test.go | 6 |
4 files changed, 134 insertions, 94 deletions
diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index cb6b709066..2b0c45d6b2 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -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") diff --git a/src/cmd/internal/obj/util.go b/src/cmd/internal/obj/util.go index 46d662c6c8..d020026445 100644 --- a/src/cmd/internal/obj/util.go +++ b/src/cmd/internal/obj/util.go @@ -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 { diff --git a/src/cmd/internal/src/pos.go b/src/cmd/internal/src/pos.go index 861d9188b1..b6816a56e0 100644 --- a/src/cmd/internal/src/pos.go +++ b/src/cmd/internal/src/pos.go @@ -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() } // ---------------------------------------------------------------------------- diff --git a/src/cmd/internal/src/pos_test.go b/src/cmd/internal/src/pos_test.go index d6131bab4c..d4cd0e7ff1 100644 --- a/src/cmd/internal/src/pos_test.go +++ b/src/cmd/internal/src/pos_test.go @@ -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) } } |
