go/src/cmd/compile/internal/gc/closure.go

665 lines
17 KiB
Go
Raw Normal View History

// Copyright 2009 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 gc
import (
"fmt"
)
// function literals aka closures
func closurehdr(ntype *Node) {
var name *Node
var a *Node
n := Nod(OCLOSURE, nil, nil)
n.Func.Ntype = ntype
n.Func.Depth = Funcdepth
n.Func.Outerfunc = Curfn
funchdr(n)
// steal ntype's argument names and
// leave a fresh copy in their place.
// references to these variables need to
// refer to the variables in the external
// function declared below; see walkclosure.
n.List.Set(ntype.List.Slice())
n.Rlist.Set(ntype.Rlist.Slice())
ntype.List.Set(nil)
ntype.Rlist.Set(nil)
for _, n1 := range n.List.Slice() {
name = n1.Left
if name != nil {
name = newname(name.Sym)
}
a = Nod(ODCLFIELD, name, n1.Right)
a.Isddd = n1.Isddd
if name != nil {
name.Isddd = a.Isddd
}
ntype.List.Append(a)
}
for _, n2 := range n.Rlist.Slice() {
name = n2.Left
if name != nil {
name = newname(name.Sym)
}
ntype.Rlist.Append(Nod(ODCLFIELD, name, n2.Right))
}
}
func closurebody(body []*Node) *Node {
if len(body) == 0 {
body = []*Node{Nod(OEMPTY, nil, nil)}
}
func_ := Curfn
func_.Nbody.Set(body)
func_.Func.Endlineno = lineno
funcbody(func_)
// closure-specific variables are hanging off the
// ordinary ones in the symbol table; see oldname.
// unhook them.
// make the list of pointers for the closure call.
for _, v := range func_.Func.Cvars.Slice() {
v.Name.Param.Closure.Name.Param.Closure = v.Name.Param.Outer
v.Name.Param.Outerexpr = oldname(v.Sym)
}
return func_
}
func typecheckclosure(func_ *Node, top int) {
for _, ln := range func_.Func.Cvars.Slice() {
n := ln.Name.Param.Closure
if !n.Name.Captured {
n.Name.Captured = true
if n.Name.Decldepth == 0 {
Fatalf("typecheckclosure: var %v does not have decldepth assigned", Nconv(n, FmtShort))
}
// Ignore assignments to the variable in straightline code
// preceding the first capturing by a closure.
if n.Name.Decldepth == decldepth {
n.Assigned = false
}
}
}
for _, ln := range func_.Func.Dcl {
if ln.Op == ONAME && (ln.Class == PPARAM || ln.Class == PPARAMOUT) {
ln.Name.Decldepth = 1
}
}
oldfn := Curfn
cmd/compile: reduce use of **Node parameters Escape analysis has a hard time with tree-like structures (see #13493 and #14858). This is unlikely to change. As a result, when invoking a function that accepts a **Node parameter, we usually allocate a *Node on the heap. This happens a whole lot. This CL changes functions from taking a **Node to acting more like append: It both modifies the input and returns a replacement for it. Because of the cascading nature of escape analysis, in order to get the benefits, I had to modify almost all such functions. The remaining functions are in racewalk and the backend. I would be happy to update them as well in a separate CL. This CL was created by manually updating the function signatures and the directly impacted bits of code. The callsites were then automatically updated using a bespoke script: https://gist.github.com/josharian/046b1be7aceae244de39 For ease of reviewing and future understanding, this CL is also broken down into four CLs, mailed separately, which show the manual and the automated changes separately. They are CLs 20990, 20991, 20992, and 20993. Passes toolstash -cmp. name old time/op new time/op delta Template 335ms ± 5% 324ms ± 5% -3.35% (p=0.000 n=23+24) Unicode 176ms ± 9% 165ms ± 6% -6.12% (p=0.000 n=23+24) GoTypes 1.10s ± 4% 1.07s ± 2% -2.77% (p=0.000 n=24+24) Compiler 5.31s ± 3% 5.15s ± 3% -2.95% (p=0.000 n=24+24) MakeBash 41.6s ± 1% 41.7s ± 2% ~ (p=0.586 n=23+23) name old alloc/op new alloc/op delta Template 63.3MB ± 0% 62.4MB ± 0% -1.36% (p=0.000 n=25+23) Unicode 42.4MB ± 0% 41.6MB ± 0% -1.99% (p=0.000 n=24+25) GoTypes 220MB ± 0% 217MB ± 0% -1.11% (p=0.000 n=25+25) Compiler 994MB ± 0% 973MB ± 0% -2.08% (p=0.000 n=24+25) name old allocs/op new allocs/op delta Template 681k ± 0% 574k ± 0% -15.71% (p=0.000 n=24+25) Unicode 518k ± 0% 413k ± 0% -20.34% (p=0.000 n=25+24) GoTypes 2.08M ± 0% 1.78M ± 0% -14.62% (p=0.000 n=25+25) Compiler 9.26M ± 0% 7.64M ± 0% -17.48% (p=0.000 n=25+25) name old text-bytes new text-bytes delta HelloSize 578k ± 0% 578k ± 0% ~ (all samples are equal) CmdGoSize 6.46M ± 0% 6.46M ± 0% ~ (all samples are equal) name old data-bytes new data-bytes delta HelloSize 128k ± 0% 128k ± 0% ~ (all samples are equal) CmdGoSize 281k ± 0% 281k ± 0% ~ (all samples are equal) name old exe-bytes new exe-bytes delta HelloSize 921k ± 0% 921k ± 0% ~ (all samples are equal) CmdGoSize 9.86M ± 0% 9.86M ± 0% ~ (all samples are equal) Change-Id: I277d95bd56d51c166ef7f560647aeaa092f3f475 Reviewed-on: https://go-review.googlesource.com/20959 Reviewed-by: Dave Cheney <dave@cheney.net> Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-03-20 08:03:31 -07:00
func_.Func.Ntype = typecheck(func_.Func.Ntype, Etype)
func_.Type = func_.Func.Ntype.Type
func_.Func.Top = top
// Type check the body now, but only if we're inside a function.
// At top level (in a variable initialization: curfn==nil) we're not
// ready to type check code yet; we'll check it later, because the
// underlying closure function we create is added to xtop.
if Curfn != nil && func_.Type != nil {
Curfn = func_
olddd := decldepth
decldepth = 1
typecheckslice(func_.Nbody.Slice(), Etop)
decldepth = olddd
Curfn = oldfn
}
// Create top-level function
xtop = append(xtop, makeclosure(func_))
}
// closurename returns name for OCLOSURE n.
// It is not as simple as it ought to be, because we typecheck nested closures
// starting from the innermost one. So when we check the inner closure,
// we don't yet have name for the outer closure. This function uses recursion
// to generate names all the way up if necessary.
var closurename_closgen int
func closurename(n *Node) *Sym {
if n.Sym != nil {
return n.Sym
}
gen := 0
outer := ""
prefix := ""
if n.Func.Outerfunc == nil {
// Global closure.
outer = "glob"
prefix = "func"
closurename_closgen++
gen = closurename_closgen
} else if n.Func.Outerfunc.Op == ODCLFUNC {
// The outermost closure inside of a named function.
outer = n.Func.Outerfunc.Func.Nname.Sym.Name
prefix = "func"
// Yes, functions can be named _.
// Can't use function closgen in such case,
// because it would lead to name clashes.
if !isblank(n.Func.Outerfunc.Func.Nname) {
n.Func.Outerfunc.Func.Closgen++
gen = n.Func.Outerfunc.Func.Closgen
} else {
closurename_closgen++
gen = closurename_closgen
}
} else if n.Func.Outerfunc.Op == OCLOSURE {
// Nested closure, recurse.
outer = closurename(n.Func.Outerfunc).Name
prefix = ""
n.Func.Outerfunc.Func.Closgen++
gen = n.Func.Outerfunc.Func.Closgen
} else {
Fatalf("closurename called for %v", Nconv(n, FmtShort))
}
n.Sym = Lookupf("%s.%s%d", outer, prefix, gen)
return n.Sym
}
func makeclosure(func_ *Node) *Node {
// wrap body in external function
// that begins by reading closure parameters.
xtype := Nod(OTFUNC, nil, nil)
xtype.List.Set(func_.List.Slice())
xtype.Rlist.Set(func_.Rlist.Slice())
// create the function
xfunc := Nod(ODCLFUNC, nil, nil)
xfunc.Func.Nname = newfuncname(closurename(func_))
xfunc.Func.Nname.Sym.Flags |= SymExported // disable export
xfunc.Func.Nname.Name.Param.Ntype = xtype
xfunc.Func.Nname.Name.Defn = xfunc
declare(xfunc.Func.Nname, PFUNC)
xfunc.Func.Nname.Name.Funcdepth = func_.Func.Depth
xfunc.Func.Depth = func_.Func.Depth
xfunc.Func.Endlineno = func_.Func.Endlineno
makefuncsym(xfunc.Func.Nname.Sym)
xfunc.Nbody.Set(func_.Nbody.Slice())
xfunc.Func.Dcl = append(func_.Func.Dcl, xfunc.Func.Dcl...)
func_.Func.Dcl = nil
if len(xfunc.Nbody.Slice()) == 0 {
Fatalf("empty body - won't generate any code")
}
cmd/compile: reduce use of **Node parameters Escape analysis has a hard time with tree-like structures (see #13493 and #14858). This is unlikely to change. As a result, when invoking a function that accepts a **Node parameter, we usually allocate a *Node on the heap. This happens a whole lot. This CL changes functions from taking a **Node to acting more like append: It both modifies the input and returns a replacement for it. Because of the cascading nature of escape analysis, in order to get the benefits, I had to modify almost all such functions. The remaining functions are in racewalk and the backend. I would be happy to update them as well in a separate CL. This CL was created by manually updating the function signatures and the directly impacted bits of code. The callsites were then automatically updated using a bespoke script: https://gist.github.com/josharian/046b1be7aceae244de39 For ease of reviewing and future understanding, this CL is also broken down into four CLs, mailed separately, which show the manual and the automated changes separately. They are CLs 20990, 20991, 20992, and 20993. Passes toolstash -cmp. name old time/op new time/op delta Template 335ms ± 5% 324ms ± 5% -3.35% (p=0.000 n=23+24) Unicode 176ms ± 9% 165ms ± 6% -6.12% (p=0.000 n=23+24) GoTypes 1.10s ± 4% 1.07s ± 2% -2.77% (p=0.000 n=24+24) Compiler 5.31s ± 3% 5.15s ± 3% -2.95% (p=0.000 n=24+24) MakeBash 41.6s ± 1% 41.7s ± 2% ~ (p=0.586 n=23+23) name old alloc/op new alloc/op delta Template 63.3MB ± 0% 62.4MB ± 0% -1.36% (p=0.000 n=25+23) Unicode 42.4MB ± 0% 41.6MB ± 0% -1.99% (p=0.000 n=24+25) GoTypes 220MB ± 0% 217MB ± 0% -1.11% (p=0.000 n=25+25) Compiler 994MB ± 0% 973MB ± 0% -2.08% (p=0.000 n=24+25) name old allocs/op new allocs/op delta Template 681k ± 0% 574k ± 0% -15.71% (p=0.000 n=24+25) Unicode 518k ± 0% 413k ± 0% -20.34% (p=0.000 n=25+24) GoTypes 2.08M ± 0% 1.78M ± 0% -14.62% (p=0.000 n=25+25) Compiler 9.26M ± 0% 7.64M ± 0% -17.48% (p=0.000 n=25+25) name old text-bytes new text-bytes delta HelloSize 578k ± 0% 578k ± 0% ~ (all samples are equal) CmdGoSize 6.46M ± 0% 6.46M ± 0% ~ (all samples are equal) name old data-bytes new data-bytes delta HelloSize 128k ± 0% 128k ± 0% ~ (all samples are equal) CmdGoSize 281k ± 0% 281k ± 0% ~ (all samples are equal) name old exe-bytes new exe-bytes delta HelloSize 921k ± 0% 921k ± 0% ~ (all samples are equal) CmdGoSize 9.86M ± 0% 9.86M ± 0% ~ (all samples are equal) Change-Id: I277d95bd56d51c166ef7f560647aeaa092f3f475 Reviewed-on: https://go-review.googlesource.com/20959 Reviewed-by: Dave Cheney <dave@cheney.net> Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-03-20 08:03:31 -07:00
xfunc = typecheck(xfunc, Etop)
xfunc.Func.Closure = func_
func_.Func.Closure = xfunc
func_.Nbody.Set(nil)
func_.List.Set(nil)
func_.Rlist.Set(nil)
return xfunc
}
// capturevars is called in a separate phase after all typechecking is done.
// It decides whether each variable captured by a closure should be captured
// by value or by reference.
// We use value capturing for values <= 128 bytes that are never reassigned
// after capturing (effectively constant).
func capturevars(xfunc *Node) {
var outer *Node
lno := lineno
lineno = xfunc.Lineno
func_ := xfunc.Func.Closure
func_.Func.Enter.Set(nil)
for _, v := range func_.Func.Cvars.Slice() {
if v.Type == nil {
// if v->type is nil, it means v looked like it was
// going to be used in the closure but wasn't.
// this happens because when parsing a, b, c := f()
// the a, b, c gets parsed as references to older
// a, b, c before the parser figures out this is a
// declaration.
v.Op = OXXX
continue
}
// type check the & of closed variables outside the closure,
// so that the outer frame also grabs them and knows they escape.
dowidth(v.Type)
outer = v.Name.Param.Outerexpr
v.Name.Param.Outerexpr = nil
// out parameters will be assigned to implicitly upon return.
if outer.Class != PPARAMOUT && !v.Name.Param.Closure.Addrtaken && !v.Name.Param.Closure.Assigned && v.Type.Width <= 128 {
v.Name.Byval = true
} else {
v.Name.Param.Closure.Addrtaken = true
outer = Nod(OADDR, outer, nil)
}
if Debug['m'] > 1 {
var name *Sym
if v.Name.Curfn != nil && v.Name.Curfn.Func.Nname != nil {
name = v.Name.Curfn.Func.Nname.Sym
}
how := "ref"
if v.Name.Byval {
how = "value"
}
Warnl(v.Lineno, "%v capturing by %s: %v (addr=%v assign=%v width=%d)", name, how, v.Sym, v.Name.Param.Closure.Addrtaken, v.Name.Param.Closure.Assigned, int32(v.Type.Width))
}
cmd/compile: reduce use of **Node parameters Escape analysis has a hard time with tree-like structures (see #13493 and #14858). This is unlikely to change. As a result, when invoking a function that accepts a **Node parameter, we usually allocate a *Node on the heap. This happens a whole lot. This CL changes functions from taking a **Node to acting more like append: It both modifies the input and returns a replacement for it. Because of the cascading nature of escape analysis, in order to get the benefits, I had to modify almost all such functions. The remaining functions are in racewalk and the backend. I would be happy to update them as well in a separate CL. This CL was created by manually updating the function signatures and the directly impacted bits of code. The callsites were then automatically updated using a bespoke script: https://gist.github.com/josharian/046b1be7aceae244de39 For ease of reviewing and future understanding, this CL is also broken down into four CLs, mailed separately, which show the manual and the automated changes separately. They are CLs 20990, 20991, 20992, and 20993. Passes toolstash -cmp. name old time/op new time/op delta Template 335ms ± 5% 324ms ± 5% -3.35% (p=0.000 n=23+24) Unicode 176ms ± 9% 165ms ± 6% -6.12% (p=0.000 n=23+24) GoTypes 1.10s ± 4% 1.07s ± 2% -2.77% (p=0.000 n=24+24) Compiler 5.31s ± 3% 5.15s ± 3% -2.95% (p=0.000 n=24+24) MakeBash 41.6s ± 1% 41.7s ± 2% ~ (p=0.586 n=23+23) name old alloc/op new alloc/op delta Template 63.3MB ± 0% 62.4MB ± 0% -1.36% (p=0.000 n=25+23) Unicode 42.4MB ± 0% 41.6MB ± 0% -1.99% (p=0.000 n=24+25) GoTypes 220MB ± 0% 217MB ± 0% -1.11% (p=0.000 n=25+25) Compiler 994MB ± 0% 973MB ± 0% -2.08% (p=0.000 n=24+25) name old allocs/op new allocs/op delta Template 681k ± 0% 574k ± 0% -15.71% (p=0.000 n=24+25) Unicode 518k ± 0% 413k ± 0% -20.34% (p=0.000 n=25+24) GoTypes 2.08M ± 0% 1.78M ± 0% -14.62% (p=0.000 n=25+25) Compiler 9.26M ± 0% 7.64M ± 0% -17.48% (p=0.000 n=25+25) name old text-bytes new text-bytes delta HelloSize 578k ± 0% 578k ± 0% ~ (all samples are equal) CmdGoSize 6.46M ± 0% 6.46M ± 0% ~ (all samples are equal) name old data-bytes new data-bytes delta HelloSize 128k ± 0% 128k ± 0% ~ (all samples are equal) CmdGoSize 281k ± 0% 281k ± 0% ~ (all samples are equal) name old exe-bytes new exe-bytes delta HelloSize 921k ± 0% 921k ± 0% ~ (all samples are equal) CmdGoSize 9.86M ± 0% 9.86M ± 0% ~ (all samples are equal) Change-Id: I277d95bd56d51c166ef7f560647aeaa092f3f475 Reviewed-on: https://go-review.googlesource.com/20959 Reviewed-by: Dave Cheney <dave@cheney.net> Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-03-20 08:03:31 -07:00
outer = typecheck(outer, Erv)
func_.Func.Enter.Append(outer)
}
lineno = lno
}
// transformclosure is called in a separate phase after escape analysis.
// It transform closure bodies to properly reference captured variables.
func transformclosure(xfunc *Node) {
lno := lineno
lineno = xfunc.Lineno
func_ := xfunc.Func.Closure
if func_.Func.Top&Ecall != 0 {
// If the closure is directly called, we transform it to a plain function call
// with variables passed as args. This avoids allocation of a closure object.
// Here we do only a part of the transformation. Walk of OCALLFUNC(OCLOSURE)
// will complete the transformation later.
// For illustration, the following closure:
// func(a int) {
// println(byval)
// byref++
// }(42)
// becomes:
// func(a int, byval int, &byref *int) {
// println(byval)
// (*&byref)++
// }(byval, &byref, 42)
// f is ONAME of the actual function.
f := xfunc.Func.Nname
// We are going to insert captured variables before input args.
var params []*Field
var decls []*Node
for _, v := range func_.Func.Cvars.Slice() {
if v.Op == OXXX {
continue
}
fld := newField()
fld.Funarg = true
if v.Name.Byval {
// If v is captured by value, we merely downgrade it to PPARAM.
v.Class = PPARAM
v.Ullman = 1
fld.Nname = v
} else {
// If v of type T is captured by reference,
// we introduce function param &v *T
// and v remains PPARAMREF with &v heapaddr
// (accesses will implicitly deref &v).
addr := newname(Lookupf("&%s", v.Sym.Name))
addr.Type = Ptrto(v.Type)
addr.Class = PPARAM
v.Name.Heapaddr = addr
fld.Nname = addr
}
fld.Type = fld.Nname.Type
fld.Sym = fld.Nname.Sym
params = append(params, fld)
decls = append(decls, fld.Nname)
}
if len(params) > 0 {
// Prepend params and decls.
f.Type.Params().SetFields(append(params, f.Type.Params().FieldSlice()...))
xfunc.Func.Dcl = append(decls, xfunc.Func.Dcl...)
}
// Recalculate param offsets.
if f.Type.Width > 0 {
Fatalf("transformclosure: width is already calculated")
}
dowidth(f.Type)
xfunc.Type = f.Type // update type of ODCLFUNC
} else {
// The closure is not called, so it is going to stay as closure.
var body []*Node
offset := int64(Widthptr)
var addr *Node
var cv *Node
for _, v := range func_.Func.Cvars.Slice() {
if v.Op == OXXX {
continue
}
// cv refers to the field inside of closure OSTRUCTLIT.
cv = Nod(OCLOSUREVAR, nil, nil)
cv.Type = v.Type
if !v.Name.Byval {
cv.Type = Ptrto(v.Type)
}
offset = Rnd(offset, int64(cv.Type.Align))
cv.Xoffset = offset
offset += cv.Type.Width
if v.Name.Byval && v.Type.Width <= int64(2*Widthptr) {
// If it is a small variable captured by value, downgrade it to PAUTO.
v.Class = PAUTO
v.Ullman = 1
xfunc.Func.Dcl = append(xfunc.Func.Dcl, v)
body = append(body, Nod(OAS, v, cv))
} else {
// Declare variable holding addresses taken from closure
// and initialize in entry prologue.
addr = newname(Lookupf("&%s", v.Sym.Name))
addr.Name.Param.Ntype = Nod(OIND, typenod(v.Type), nil)
addr.Class = PAUTO
addr.Used = true
addr.Name.Curfn = xfunc
xfunc.Func.Dcl = append(xfunc.Func.Dcl, addr)
v.Name.Heapaddr = addr
if v.Name.Byval {
cv = Nod(OADDR, cv, nil)
}
body = append(body, Nod(OAS, addr, cv))
}
}
if len(body) > 0 {
typecheckslice(body, Etop)
walkstmtlist(body)
xfunc.Func.Enter.Set(body)
xfunc.Func.Needctxt = true
}
}
lineno = lno
}
func walkclosure(func_ *Node, init *Nodes) *Node {
// If no closure vars, don't bother wrapping.
if len(func_.Func.Cvars.Slice()) == 0 {
return func_.Func.Closure.Func.Nname
}
// Create closure in the form of a composite literal.
// supposing the closure captures an int i and a string s
// and has one float64 argument and no results,
// the generated code looks like:
//
// clos = &struct{.F uintptr; i *int; s *string}{func.1, &i, &s}
//
// The use of the struct provides type information to the garbage
// collector so that it can walk the closure. We could use (in this case)
// [3]unsafe.Pointer instead, but that would leave the gc in the dark.
// The information appears in the binary in the form of type descriptors;
// the struct is unnamed so that closures in multiple packages with the
// same struct type can share the descriptor.
typ := Nod(OTSTRUCT, nil, nil)
typ.List.Set1(Nod(ODCLFIELD, newname(Lookup(".F")), typenod(Types[TUINTPTR])))
var typ1 *Node
for _, v := range func_.Func.Cvars.Slice() {
if v.Op == OXXX {
continue
}
typ1 = typenod(v.Type)
if !v.Name.Byval {
typ1 = Nod(OIND, typ1, nil)
}
typ.List.Append(Nod(ODCLFIELD, newname(v.Sym), typ1))
}
clos := Nod(OCOMPLIT, nil, Nod(OIND, typ, nil))
clos.Esc = func_.Esc
clos.Right.Implicit = true
clos.List.Set(append([]*Node{Nod(OCFUNC, func_.Func.Closure.Func.Nname, nil)}, func_.Func.Enter.Slice()...))
// Force type conversion from *struct to the func type.
clos = Nod(OCONVNOP, clos, nil)
clos.Type = func_.Type
cmd/compile: reduce use of **Node parameters Escape analysis has a hard time with tree-like structures (see #13493 and #14858). This is unlikely to change. As a result, when invoking a function that accepts a **Node parameter, we usually allocate a *Node on the heap. This happens a whole lot. This CL changes functions from taking a **Node to acting more like append: It both modifies the input and returns a replacement for it. Because of the cascading nature of escape analysis, in order to get the benefits, I had to modify almost all such functions. The remaining functions are in racewalk and the backend. I would be happy to update them as well in a separate CL. This CL was created by manually updating the function signatures and the directly impacted bits of code. The callsites were then automatically updated using a bespoke script: https://gist.github.com/josharian/046b1be7aceae244de39 For ease of reviewing and future understanding, this CL is also broken down into four CLs, mailed separately, which show the manual and the automated changes separately. They are CLs 20990, 20991, 20992, and 20993. Passes toolstash -cmp. name old time/op new time/op delta Template 335ms ± 5% 324ms ± 5% -3.35% (p=0.000 n=23+24) Unicode 176ms ± 9% 165ms ± 6% -6.12% (p=0.000 n=23+24) GoTypes 1.10s ± 4% 1.07s ± 2% -2.77% (p=0.000 n=24+24) Compiler 5.31s ± 3% 5.15s ± 3% -2.95% (p=0.000 n=24+24) MakeBash 41.6s ± 1% 41.7s ± 2% ~ (p=0.586 n=23+23) name old alloc/op new alloc/op delta Template 63.3MB ± 0% 62.4MB ± 0% -1.36% (p=0.000 n=25+23) Unicode 42.4MB ± 0% 41.6MB ± 0% -1.99% (p=0.000 n=24+25) GoTypes 220MB ± 0% 217MB ± 0% -1.11% (p=0.000 n=25+25) Compiler 994MB ± 0% 973MB ± 0% -2.08% (p=0.000 n=24+25) name old allocs/op new allocs/op delta Template 681k ± 0% 574k ± 0% -15.71% (p=0.000 n=24+25) Unicode 518k ± 0% 413k ± 0% -20.34% (p=0.000 n=25+24) GoTypes 2.08M ± 0% 1.78M ± 0% -14.62% (p=0.000 n=25+25) Compiler 9.26M ± 0% 7.64M ± 0% -17.48% (p=0.000 n=25+25) name old text-bytes new text-bytes delta HelloSize 578k ± 0% 578k ± 0% ~ (all samples are equal) CmdGoSize 6.46M ± 0% 6.46M ± 0% ~ (all samples are equal) name old data-bytes new data-bytes delta HelloSize 128k ± 0% 128k ± 0% ~ (all samples are equal) CmdGoSize 281k ± 0% 281k ± 0% ~ (all samples are equal) name old exe-bytes new exe-bytes delta HelloSize 921k ± 0% 921k ± 0% ~ (all samples are equal) CmdGoSize 9.86M ± 0% 9.86M ± 0% ~ (all samples are equal) Change-Id: I277d95bd56d51c166ef7f560647aeaa092f3f475 Reviewed-on: https://go-review.googlesource.com/20959 Reviewed-by: Dave Cheney <dave@cheney.net> Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-03-20 08:03:31 -07:00
clos = typecheck(clos, Erv)
// typecheck will insert a PTRLIT node under CONVNOP,
// tag it with escape analysis result.
clos.Left.Esc = func_.Esc
// non-escaping temp to use, if any.
// orderexpr did not compute the type; fill it in now.
if x := prealloc[func_]; x != nil {
x.Type = clos.Left.Left.Type
x.Orig.Type = x.Type
clos.Left.Right = x
delete(prealloc, func_)
}
return walkexpr(clos, init)
}
func typecheckpartialcall(fn *Node, sym *Sym) {
switch fn.Op {
case ODOTINTER, ODOTMETH:
break
default:
Fatalf("invalid typecheckpartialcall")
}
// Create top-level function.
xfunc := makepartialcall(fn, fn.Type, sym)
fn.Func = xfunc.Func
fn.Right = newname(sym)
fn.Op = OCALLPART
fn.Type = xfunc.Type
}
var makepartialcall_gopkg *Pkg
func makepartialcall(fn *Node, t0 *Type, meth *Sym) *Node {
var p string
rcvrtype := fn.Left.Type
if exportname(meth.Name) {
p = fmt.Sprintf("(%v).%s-fm", Tconv(rcvrtype, FmtLeft|FmtShort), meth.Name)
} else {
p = fmt.Sprintf("(%v).(%v)-fm", Tconv(rcvrtype, FmtLeft|FmtShort), Sconv(meth, FmtLeft))
}
basetype := rcvrtype
if Isptr[rcvrtype.Etype] {
basetype = basetype.Elem()
}
if basetype.Etype != TINTER && basetype.Sym == nil {
Fatalf("missing base type for %v", rcvrtype)
}
var spkg *Pkg
if basetype.Sym != nil {
spkg = basetype.Sym.Pkg
}
if spkg == nil {
if makepartialcall_gopkg == nil {
makepartialcall_gopkg = mkpkg("go")
}
spkg = makepartialcall_gopkg
}
sym := Pkglookup(p, spkg)
if sym.Flags&SymUniq != 0 {
return sym.Def
}
sym.Flags |= SymUniq
savecurfn := Curfn
Curfn = nil
xtype := Nod(OTFUNC, nil, nil)
i := 0
var l []*Node
var callargs []*Node
ddd := false
xfunc := Nod(ODCLFUNC, nil, nil)
Curfn = xfunc
var fld *Node
var n *Node
for _, t := range t0.Params().Fields().Slice() {
n = newname(LookupN("a", i))
i++
n.Class = PPARAM
xfunc.Func.Dcl = append(xfunc.Func.Dcl, n)
callargs = append(callargs, n)
fld = Nod(ODCLFIELD, n, typenod(t.Type))
if t.Isddd {
fld.Isddd = true
ddd = true
}
l = append(l, fld)
}
xtype.List.Set(l)
i = 0
l = nil
var retargs []*Node
for _, t := range t0.Results().Fields().Slice() {
n = newname(LookupN("r", i))
i++
n.Class = PPARAMOUT
xfunc.Func.Dcl = append(xfunc.Func.Dcl, n)
retargs = append(retargs, n)
l = append(l, Nod(ODCLFIELD, n, typenod(t.Type)))
}
xtype.Rlist.Set(l)
xfunc.Func.Dupok = true
xfunc.Func.Nname = newfuncname(sym)
xfunc.Func.Nname.Sym.Flags |= SymExported // disable export
xfunc.Func.Nname.Name.Param.Ntype = xtype
xfunc.Func.Nname.Name.Defn = xfunc
declare(xfunc.Func.Nname, PFUNC)
// Declare and initialize variable holding receiver.
xfunc.Func.Needctxt = true
cv := Nod(OCLOSUREVAR, nil, nil)
cv.Xoffset = int64(Widthptr)
cv.Type = rcvrtype
if int(cv.Type.Align) > Widthptr {
cv.Xoffset = int64(cv.Type.Align)
}
ptr := Nod(ONAME, nil, nil)
ptr.Sym = Lookup("rcvr")
ptr.Class = PAUTO
ptr.Addable = true
ptr.Ullman = 1
ptr.Used = true
ptr.Name.Curfn = xfunc
ptr.Xoffset = 0
xfunc.Func.Dcl = append(xfunc.Func.Dcl, ptr)
var body []*Node
if Isptr[rcvrtype.Etype] || Isinter(rcvrtype) {
ptr.Name.Param.Ntype = typenod(rcvrtype)
body = append(body, Nod(OAS, ptr, cv))
} else {
ptr.Name.Param.Ntype = typenod(Ptrto(rcvrtype))
body = append(body, Nod(OAS, ptr, Nod(OADDR, cv, nil)))
}
call := Nod(OCALL, NodSym(OXDOT, ptr, meth), nil)
call.List.Set(callargs)
call.Isddd = ddd
if t0.Results().NumFields() == 0 {
body = append(body, call)
} else {
n := Nod(OAS2, nil, nil)
n.List.Set(retargs)
n.Rlist.Set1(call)
body = append(body, n)
n = Nod(ORETURN, nil, nil)
body = append(body, n)
}
xfunc.Nbody.Set(body)
cmd/compile: reduce use of **Node parameters Escape analysis has a hard time with tree-like structures (see #13493 and #14858). This is unlikely to change. As a result, when invoking a function that accepts a **Node parameter, we usually allocate a *Node on the heap. This happens a whole lot. This CL changes functions from taking a **Node to acting more like append: It both modifies the input and returns a replacement for it. Because of the cascading nature of escape analysis, in order to get the benefits, I had to modify almost all such functions. The remaining functions are in racewalk and the backend. I would be happy to update them as well in a separate CL. This CL was created by manually updating the function signatures and the directly impacted bits of code. The callsites were then automatically updated using a bespoke script: https://gist.github.com/josharian/046b1be7aceae244de39 For ease of reviewing and future understanding, this CL is also broken down into four CLs, mailed separately, which show the manual and the automated changes separately. They are CLs 20990, 20991, 20992, and 20993. Passes toolstash -cmp. name old time/op new time/op delta Template 335ms ± 5% 324ms ± 5% -3.35% (p=0.000 n=23+24) Unicode 176ms ± 9% 165ms ± 6% -6.12% (p=0.000 n=23+24) GoTypes 1.10s ± 4% 1.07s ± 2% -2.77% (p=0.000 n=24+24) Compiler 5.31s ± 3% 5.15s ± 3% -2.95% (p=0.000 n=24+24) MakeBash 41.6s ± 1% 41.7s ± 2% ~ (p=0.586 n=23+23) name old alloc/op new alloc/op delta Template 63.3MB ± 0% 62.4MB ± 0% -1.36% (p=0.000 n=25+23) Unicode 42.4MB ± 0% 41.6MB ± 0% -1.99% (p=0.000 n=24+25) GoTypes 220MB ± 0% 217MB ± 0% -1.11% (p=0.000 n=25+25) Compiler 994MB ± 0% 973MB ± 0% -2.08% (p=0.000 n=24+25) name old allocs/op new allocs/op delta Template 681k ± 0% 574k ± 0% -15.71% (p=0.000 n=24+25) Unicode 518k ± 0% 413k ± 0% -20.34% (p=0.000 n=25+24) GoTypes 2.08M ± 0% 1.78M ± 0% -14.62% (p=0.000 n=25+25) Compiler 9.26M ± 0% 7.64M ± 0% -17.48% (p=0.000 n=25+25) name old text-bytes new text-bytes delta HelloSize 578k ± 0% 578k ± 0% ~ (all samples are equal) CmdGoSize 6.46M ± 0% 6.46M ± 0% ~ (all samples are equal) name old data-bytes new data-bytes delta HelloSize 128k ± 0% 128k ± 0% ~ (all samples are equal) CmdGoSize 281k ± 0% 281k ± 0% ~ (all samples are equal) name old exe-bytes new exe-bytes delta HelloSize 921k ± 0% 921k ± 0% ~ (all samples are equal) CmdGoSize 9.86M ± 0% 9.86M ± 0% ~ (all samples are equal) Change-Id: I277d95bd56d51c166ef7f560647aeaa092f3f475 Reviewed-on: https://go-review.googlesource.com/20959 Reviewed-by: Dave Cheney <dave@cheney.net> Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-03-20 08:03:31 -07:00
xfunc = typecheck(xfunc, Etop)
sym.Def = xfunc
xtop = append(xtop, xfunc)
Curfn = savecurfn
return xfunc
}
func walkpartialcall(n *Node, init *Nodes) *Node {
// Create closure in the form of a composite literal.
// For x.M with receiver (x) type T, the generated code looks like:
//
// clos = &struct{F uintptr; R T}{M.T·f, x}
//
// Like walkclosure above.
if Isinter(n.Left.Type) {
// Trigger panic for method on nil interface now.
// Otherwise it happens in the wrapper and is confusing.
n.Left = cheapexpr(n.Left, init)
checknil(n.Left, init)
}
typ := Nod(OTSTRUCT, nil, nil)
typ.List.Set1(Nod(ODCLFIELD, newname(Lookup("F")), typenod(Types[TUINTPTR])))
typ.List.Append(Nod(ODCLFIELD, newname(Lookup("R")), typenod(n.Left.Type)))
clos := Nod(OCOMPLIT, nil, Nod(OIND, typ, nil))
clos.Esc = n.Esc
clos.Right.Implicit = true
clos.List.Set1(Nod(OCFUNC, n.Func.Nname, nil))
clos.List.Append(n.Left)
// Force type conversion from *struct to the func type.
clos = Nod(OCONVNOP, clos, nil)
clos.Type = n.Type
cmd/compile: reduce use of **Node parameters Escape analysis has a hard time with tree-like structures (see #13493 and #14858). This is unlikely to change. As a result, when invoking a function that accepts a **Node parameter, we usually allocate a *Node on the heap. This happens a whole lot. This CL changes functions from taking a **Node to acting more like append: It both modifies the input and returns a replacement for it. Because of the cascading nature of escape analysis, in order to get the benefits, I had to modify almost all such functions. The remaining functions are in racewalk and the backend. I would be happy to update them as well in a separate CL. This CL was created by manually updating the function signatures and the directly impacted bits of code. The callsites were then automatically updated using a bespoke script: https://gist.github.com/josharian/046b1be7aceae244de39 For ease of reviewing and future understanding, this CL is also broken down into four CLs, mailed separately, which show the manual and the automated changes separately. They are CLs 20990, 20991, 20992, and 20993. Passes toolstash -cmp. name old time/op new time/op delta Template 335ms ± 5% 324ms ± 5% -3.35% (p=0.000 n=23+24) Unicode 176ms ± 9% 165ms ± 6% -6.12% (p=0.000 n=23+24) GoTypes 1.10s ± 4% 1.07s ± 2% -2.77% (p=0.000 n=24+24) Compiler 5.31s ± 3% 5.15s ± 3% -2.95% (p=0.000 n=24+24) MakeBash 41.6s ± 1% 41.7s ± 2% ~ (p=0.586 n=23+23) name old alloc/op new alloc/op delta Template 63.3MB ± 0% 62.4MB ± 0% -1.36% (p=0.000 n=25+23) Unicode 42.4MB ± 0% 41.6MB ± 0% -1.99% (p=0.000 n=24+25) GoTypes 220MB ± 0% 217MB ± 0% -1.11% (p=0.000 n=25+25) Compiler 994MB ± 0% 973MB ± 0% -2.08% (p=0.000 n=24+25) name old allocs/op new allocs/op delta Template 681k ± 0% 574k ± 0% -15.71% (p=0.000 n=24+25) Unicode 518k ± 0% 413k ± 0% -20.34% (p=0.000 n=25+24) GoTypes 2.08M ± 0% 1.78M ± 0% -14.62% (p=0.000 n=25+25) Compiler 9.26M ± 0% 7.64M ± 0% -17.48% (p=0.000 n=25+25) name old text-bytes new text-bytes delta HelloSize 578k ± 0% 578k ± 0% ~ (all samples are equal) CmdGoSize 6.46M ± 0% 6.46M ± 0% ~ (all samples are equal) name old data-bytes new data-bytes delta HelloSize 128k ± 0% 128k ± 0% ~ (all samples are equal) CmdGoSize 281k ± 0% 281k ± 0% ~ (all samples are equal) name old exe-bytes new exe-bytes delta HelloSize 921k ± 0% 921k ± 0% ~ (all samples are equal) CmdGoSize 9.86M ± 0% 9.86M ± 0% ~ (all samples are equal) Change-Id: I277d95bd56d51c166ef7f560647aeaa092f3f475 Reviewed-on: https://go-review.googlesource.com/20959 Reviewed-by: Dave Cheney <dave@cheney.net> Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-03-20 08:03:31 -07:00
clos = typecheck(clos, Erv)
// typecheck will insert a PTRLIT node under CONVNOP,
// tag it with escape analysis result.
clos.Left.Esc = n.Esc
// non-escaping temp to use, if any.
// orderexpr did not compute the type; fill it in now.
if x := prealloc[n]; x != nil {
x.Type = clos.Left.Left.Type
x.Orig.Type = x.Type
clos.Left.Right = x
delete(prealloc, n)
}
return walkexpr(clos, init)
}