mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
The existing bulk/cached Prog allocator, Ctxt.NewProg, is not concurrency-safe. This CL moves Prog allocation to its clients, the compiler and the assembler. The assembler is so fast and generates so few Progs that it does not need optimization of Prog allocation. I could not generate measureable changes. And even if I could, the assembly is a miniscule portion of build times. The compiler already has a natural place to manage Prog allocation; this CL migrates the Prog cache there. It will be made concurrency-safe in a later CL by partitioning the Prog cache into chunks and assigning each chunk to a different goroutine to manage. This CL does cause a performance degradation when the compiler is invoked with the -S flag (to dump assembly). However, such usage is rare and almost always done manually. The one instance I know of in a test is TestAssembly in cmd/compile/internal/gc, and I did not detect a measurable performance impact there. Passes toolstash-check -all. Minor compiler performance impact. Updates #15756 Performance impact from just this CL: name old time/op new time/op delta Template 213ms ± 4% 213ms ± 4% ~ (p=0.571 n=49+49) Unicode 89.1ms ± 3% 89.4ms ± 3% ~ (p=0.388 n=47+48) GoTypes 581ms ± 2% 584ms ± 3% +0.56% (p=0.019 n=47+48) SSA 6.48s ± 2% 6.53s ± 2% +0.84% (p=0.000 n=47+49) Flate 128ms ± 4% 128ms ± 4% ~ (p=0.832 n=49+49) GoParser 152ms ± 3% 152ms ± 3% ~ (p=0.815 n=48+47) Reflect 371ms ± 4% 371ms ± 3% ~ (p=0.617 n=50+47) Tar 112ms ± 4% 112ms ± 3% ~ (p=0.724 n=49+49) XML 208ms ± 3% 208ms ± 4% ~ (p=0.678 n=49+50) [Geo mean] 284ms 285ms +0.18% name old user-ns/op new user-ns/op delta Template 251M ± 7% 252M ±11% ~ (p=0.704 n=49+50) Unicode 107M ± 7% 108M ± 5% +1.25% (p=0.036 n=50+49) GoTypes 738M ± 3% 740M ± 3% ~ (p=0.305 n=49+48) SSA 8.83G ± 2% 8.86G ± 4% ~ (p=0.098 n=47+50) Flate 146M ± 6% 147M ± 3% ~ (p=0.584 n=48+41) GoParser 178M ± 6% 179M ± 5% +0.93% (p=0.036 n=49+48) Reflect 441M ± 4% 446M ± 7% ~ (p=0.218 n=44+49) Tar 126M ± 5% 126M ± 5% ~ (p=0.766 n=48+49) XML 245M ± 5% 244M ± 4% ~ (p=0.359 n=50+50) [Geo mean] 341M 342M +0.51% Performance impact from this CL combined with its parent: name old time/op new time/op delta Template 213ms ± 3% 214ms ± 4% ~ (p=0.685 n=47+50) Unicode 89.8ms ± 6% 90.5ms ± 6% ~ (p=0.055 n=50+50) GoTypes 584ms ± 3% 585ms ± 2% ~ (p=0.710 n=49+47) SSA 6.50s ± 2% 6.53s ± 2% +0.39% (p=0.011 n=46+50) Flate 128ms ± 3% 128ms ± 4% ~ (p=0.855 n=47+49) GoParser 152ms ± 3% 152ms ± 3% ~ (p=0.666 n=49+49) Reflect 371ms ± 3% 372ms ± 3% ~ (p=0.298 n=48+48) Tar 112ms ± 5% 113ms ± 3% ~ (p=0.107 n=49+49) XML 208ms ± 3% 208ms ± 2% ~ (p=0.881 n=50+49) [Geo mean] 285ms 285ms +0.26% name old user-ns/op new user-ns/op delta Template 254M ± 9% 252M ± 8% ~ (p=0.290 n=49+50) Unicode 106M ± 6% 108M ± 7% +1.44% (p=0.034 n=50+50) GoTypes 741M ± 4% 743M ± 4% ~ (p=0.992 n=50+49) SSA 8.86G ± 2% 8.83G ± 3% ~ (p=0.158 n=47+49) Flate 147M ± 4% 148M ± 5% ~ (p=0.832 n=50+49) GoParser 179M ± 5% 178M ± 5% ~ (p=0.370 n=48+50) Reflect 441M ± 6% 445M ± 7% ~ (p=0.246 n=45+47) Tar 126M ± 6% 126M ± 6% ~ (p=0.815 n=49+50) XML 244M ± 3% 245M ± 4% ~ (p=0.190 n=50+50) [Geo mean] 342M 342M +0.17% Change-Id: I020f1c079d495fbe2e15ccb51e1ea2cc1b5a1855 Reviewed-on: https://go-review.googlesource.com/39634 Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
352 lines
9.3 KiB
Go
352 lines
9.3 KiB
Go
// Derived from Inferno utils/6c/txt.c
|
|
// https://bitbucket.org/inferno-os/inferno-os/src/default/utils/6c/txt.c
|
|
//
|
|
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
|
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
|
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
|
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
|
// Portions Copyright © 2004,2006 Bruce Ellis
|
|
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
|
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
|
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
package gc
|
|
|
|
import (
|
|
"cmd/internal/obj"
|
|
"cmd/internal/src"
|
|
)
|
|
|
|
var sharedProgArray *[10000]obj.Prog // *T instead of T to work around issue 19839
|
|
|
|
func init() {
|
|
sharedProgArray = new([10000]obj.Prog)
|
|
}
|
|
|
|
// Progs accumulates Progs for a function and converts them into machine code.
|
|
type Progs struct {
|
|
Text *obj.Prog // ATEXT Prog for this function
|
|
next *obj.Prog // next Prog
|
|
pc int64 // virtual PC; count of Progs
|
|
pos src.XPos // position to use for new Progs
|
|
curfn *Node // fn these Progs are for
|
|
progcache []obj.Prog // local progcache
|
|
cacheidx int // first free element of progcache
|
|
}
|
|
|
|
// newProgs returns a new Progs for fn.
|
|
func newProgs(fn *Node) *Progs {
|
|
pp := new(Progs)
|
|
if Ctxt.CanReuseProgs() {
|
|
pp.progcache = sharedProgArray[:]
|
|
}
|
|
pp.curfn = fn
|
|
|
|
// prime the pump
|
|
pp.next = pp.NewProg()
|
|
pp.clearp(pp.next)
|
|
|
|
pp.pos = fn.Pos
|
|
pp.settext(fn)
|
|
return pp
|
|
}
|
|
|
|
func (pp *Progs) NewProg() *obj.Prog {
|
|
if pp.cacheidx < len(pp.progcache) {
|
|
p := &pp.progcache[pp.cacheidx]
|
|
p.Ctxt = Ctxt
|
|
pp.cacheidx++
|
|
return p
|
|
}
|
|
p := new(obj.Prog)
|
|
p.Ctxt = Ctxt
|
|
return p
|
|
}
|
|
|
|
// Flush converts from pp to machine code.
|
|
func (pp *Progs) Flush() {
|
|
plist := &obj.Plist{Firstpc: pp.Text, Curfn: pp.curfn}
|
|
obj.Flushplist(Ctxt, plist, pp.NewProg)
|
|
}
|
|
|
|
// Free clears pp and any associated resources.
|
|
func (pp *Progs) Free() {
|
|
if Ctxt.CanReuseProgs() {
|
|
// Clear progs to enable GC and avoid abuse.
|
|
s := pp.progcache[:pp.cacheidx]
|
|
for i := range s {
|
|
s[i] = obj.Prog{}
|
|
}
|
|
}
|
|
// Clear pp to avoid abuse.
|
|
*pp = Progs{}
|
|
}
|
|
|
|
// Prog adds a Prog with instruction As to pp.
|
|
func (pp *Progs) Prog(as obj.As) *obj.Prog {
|
|
p := pp.next
|
|
pp.next = pp.NewProg()
|
|
pp.clearp(pp.next)
|
|
p.Link = pp.next
|
|
|
|
if !pp.pos.IsKnown() && Debug['K'] != 0 {
|
|
Warn("prog: unknown position (line 0)")
|
|
}
|
|
|
|
p.As = as
|
|
p.Pos = pp.pos
|
|
return p
|
|
}
|
|
|
|
func (pp *Progs) clearp(p *obj.Prog) {
|
|
obj.Nopout(p)
|
|
p.As = obj.AEND
|
|
p.Pc = pp.pc
|
|
pp.pc++
|
|
}
|
|
|
|
func (pp *Progs) Appendpp(p *obj.Prog, as obj.As, ftype obj.AddrType, freg int16, foffset int64, ttype obj.AddrType, treg int16, toffset int64) *obj.Prog {
|
|
q := pp.NewProg()
|
|
pp.clearp(q)
|
|
q.As = as
|
|
q.Pos = p.Pos
|
|
q.From.Type = ftype
|
|
q.From.Reg = freg
|
|
q.From.Offset = foffset
|
|
q.To.Type = ttype
|
|
q.To.Reg = treg
|
|
q.To.Offset = toffset
|
|
q.Link = p.Link
|
|
p.Link = q
|
|
return q
|
|
}
|
|
|
|
func (pp *Progs) settext(fn *Node) {
|
|
if pp.Text != nil {
|
|
Fatalf("Progs.settext called twice")
|
|
}
|
|
|
|
ptxt := pp.Prog(obj.ATEXT)
|
|
if nam := fn.Func.Nname; !isblank(nam) {
|
|
ptxt.From.Type = obj.TYPE_MEM
|
|
ptxt.From.Name = obj.NAME_EXTERN
|
|
ptxt.From.Sym = Linksym(nam.Sym)
|
|
if fn.Func.Pragma&Systemstack != 0 {
|
|
ptxt.From.Sym.Set(obj.AttrCFunc, true)
|
|
}
|
|
}
|
|
|
|
ptxt.From3 = new(obj.Addr)
|
|
if fn.Func.Dupok() {
|
|
ptxt.From3.Offset |= obj.DUPOK
|
|
}
|
|
if fn.Func.Wrapper() {
|
|
ptxt.From3.Offset |= obj.WRAPPER
|
|
}
|
|
if fn.Func.NoFramePointer() {
|
|
ptxt.From3.Offset |= obj.NOFRAME
|
|
}
|
|
if fn.Func.Needctxt() {
|
|
ptxt.From3.Offset |= obj.NEEDCTXT
|
|
}
|
|
if fn.Func.Pragma&Nosplit != 0 {
|
|
ptxt.From3.Offset |= obj.NOSPLIT
|
|
}
|
|
if fn.Func.ReflectMethod() {
|
|
ptxt.From3.Offset |= obj.REFLECTMETHOD
|
|
}
|
|
|
|
// Clumsy but important.
|
|
// See test/recover.go for test cases and src/reflect/value.go
|
|
// for the actual functions being considered.
|
|
if myimportpath == "reflect" {
|
|
switch fn.Func.Nname.Sym.Name {
|
|
case "callReflect", "callMethod":
|
|
ptxt.From3.Offset |= obj.WRAPPER
|
|
}
|
|
}
|
|
|
|
pp.Text = ptxt
|
|
}
|
|
|
|
func ggloblnod(nam *Node) {
|
|
s := Linksym(nam.Sym)
|
|
s.Gotype = Linksym(ngotype(nam))
|
|
flags := 0
|
|
if nam.Name.Readonly() {
|
|
flags = obj.RODATA
|
|
}
|
|
if nam.Type != nil && !haspointers(nam.Type) {
|
|
flags |= obj.NOPTR
|
|
}
|
|
Ctxt.Globl(s, nam.Type.Width, flags)
|
|
}
|
|
|
|
func ggloblsym(s *Sym, width int32, flags int16) {
|
|
ggloblLSym(Linksym(s), width, flags)
|
|
}
|
|
|
|
func ggloblLSym(s *obj.LSym, width int32, flags int16) {
|
|
if flags&obj.LOCAL != 0 {
|
|
s.Set(obj.AttrLocal, true)
|
|
flags &^= obj.LOCAL
|
|
}
|
|
Ctxt.Globl(s, int64(width), int(flags))
|
|
}
|
|
|
|
func isfat(t *Type) bool {
|
|
if t != nil {
|
|
switch t.Etype {
|
|
case TSTRUCT, TARRAY, TSLICE, TSTRING,
|
|
TINTER: // maybe remove later
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func Addrconst(a *obj.Addr, v int64) {
|
|
a.Sym = nil
|
|
a.Type = obj.TYPE_CONST
|
|
a.Offset = v
|
|
}
|
|
|
|
// nodarg returns a Node for the function argument denoted by t,
|
|
// which is either the entire function argument or result struct (t is a struct *Type)
|
|
// or a specific argument (t is a *Field within a struct *Type).
|
|
//
|
|
// If fp is 0, the node is for use by a caller invoking the given
|
|
// function, preparing the arguments before the call
|
|
// or retrieving the results after the call.
|
|
// In this case, the node will correspond to an outgoing argument
|
|
// slot like 8(SP).
|
|
//
|
|
// If fp is 1, the node is for use by the function itself
|
|
// (the callee), to retrieve its arguments or write its results.
|
|
// In this case the node will be an ONAME with an appropriate
|
|
// type and offset.
|
|
func nodarg(t interface{}, fp int) *Node {
|
|
var n *Node
|
|
|
|
var funarg Funarg
|
|
switch t := t.(type) {
|
|
default:
|
|
Fatalf("bad nodarg %T(%v)", t, t)
|
|
|
|
case *Type:
|
|
// Entire argument struct, not just one arg
|
|
if !t.IsFuncArgStruct() {
|
|
Fatalf("nodarg: bad type %v", t)
|
|
}
|
|
funarg = t.StructType().Funarg
|
|
|
|
// Build fake variable name for whole arg struct.
|
|
n = newname(lookup(".args"))
|
|
n.Type = t
|
|
first := t.Field(0)
|
|
if first == nil {
|
|
Fatalf("nodarg: bad struct")
|
|
}
|
|
if first.Offset == BADWIDTH {
|
|
Fatalf("nodarg: offset not computed for %v", t)
|
|
}
|
|
n.Xoffset = first.Offset
|
|
|
|
case *Field:
|
|
funarg = t.Funarg
|
|
if fp == 1 {
|
|
// NOTE(rsc): This should be using t.Nname directly,
|
|
// except in the case where t.Nname.Sym is the blank symbol and
|
|
// so the assignment would be discarded during code generation.
|
|
// In that case we need to make a new node, and there is no harm
|
|
// in optimization passes to doing so. But otherwise we should
|
|
// definitely be using the actual declaration and not a newly built node.
|
|
// The extra Fatalf checks here are verifying that this is the case,
|
|
// without changing the actual logic (at time of writing, it's getting
|
|
// toward time for the Go 1.7 beta).
|
|
// At some quieter time (assuming we've never seen these Fatalfs happen)
|
|
// we could change this code to use "expect" directly.
|
|
expect := t.Nname
|
|
if expect.isParamHeapCopy() {
|
|
expect = expect.Name.Param.Stackcopy
|
|
}
|
|
|
|
for _, n := range Curfn.Func.Dcl {
|
|
if (n.Class == PPARAM || n.Class == PPARAMOUT) && !isblanksym(t.Sym) && n.Sym == t.Sym {
|
|
if n != expect {
|
|
Fatalf("nodarg: unexpected node: %v (%p %v) vs %v (%p %v)", n, n, n.Op, t.Nname, t.Nname, t.Nname.Op)
|
|
}
|
|
return n
|
|
}
|
|
}
|
|
|
|
if !isblanksym(expect.Sym) {
|
|
Fatalf("nodarg: did not find node in dcl list: %v", expect)
|
|
}
|
|
}
|
|
|
|
// Build fake name for individual variable.
|
|
// This is safe because if there was a real declared name
|
|
// we'd have used it above.
|
|
n = newname(lookup("__"))
|
|
n.Type = t.Type
|
|
if t.Offset == BADWIDTH {
|
|
Fatalf("nodarg: offset not computed for %v", t)
|
|
}
|
|
n.Xoffset = t.Offset
|
|
n.Orig = t.Nname
|
|
}
|
|
|
|
// Rewrite argument named _ to __,
|
|
// or else the assignment to _ will be
|
|
// discarded during code generation.
|
|
if isblank(n) {
|
|
n.Sym = lookup("__")
|
|
}
|
|
|
|
switch fp {
|
|
default:
|
|
Fatalf("bad fp")
|
|
|
|
case 0: // preparing arguments for call
|
|
n.Op = OINDREGSP
|
|
n.Xoffset += Ctxt.FixedFrameSize()
|
|
|
|
case 1: // reading arguments inside call
|
|
n.Class = PPARAM
|
|
if funarg == FunargResults {
|
|
n.Class = PPARAMOUT
|
|
}
|
|
}
|
|
|
|
n.Typecheck = 1
|
|
n.SetAddrtaken(true) // keep optimizers at bay
|
|
return n
|
|
}
|
|
|
|
func Patch(p *obj.Prog, to *obj.Prog) {
|
|
if p.To.Type != obj.TYPE_BRANCH {
|
|
Fatalf("patch: not a branch")
|
|
}
|
|
p.To.Val = to
|
|
p.To.Offset = to.Pc
|
|
}
|