mirror of
https://github.com/golang/go.git
synced 2026-06-27 19:30:52 +00:00
cmd/compile: handle multiply-assigned func vars in escape analysis
Generalize FuncSingleAssignment to FuncAssignments, tracking all
non-zero assignments to func-typed variables. When multiple
callees are known, escape analysis merges their parameter escape
behaviors via teeHole.
compilebench results (this CL vs parent):
│ parent.txt │ commit.txt │
│ sec/op │ sec/op vs base │
Template 150.8m ± 12% 149.6m ± 12% ~ (p=0.589 n=6)
Unicode 120.2m ± 11% 117.2m ± 5% ~ (p=0.699 n=6)
GoTypes 890.6m ± 3% 884.8m ± 2% ~ (p=0.310 n=6)
Compiler 161.2m ± 13% 154.4m ± 4% ~ (p=0.180 n=6)
SSA 7.023 ± 1% 7.021 ± 1% ~ (p=0.485 n=6)
Flate 162.6m ± 6% 162.0m ± 6% ~ (p=0.699 n=6)
GoParser 176.9m ± 7% 178.0m ± 6% ~ (p=0.699 n=6)
Reflect 393.2m ± 4% 389.5m ± 5% ~ (p=0.699 n=6)
Tar 171.5m ± 14% 172.9m ± 5% ~ (p=0.937 n=6)
XML 197.1m ± 6% 197.7m ± 2% ~ (p=0.818 n=6)
LinkCompiler 650.5m ± 4% 637.0m ± 2% ~ (p=0.394 n=6)
ExternalLinkCompiler 2.196 ± 1% 2.206 ± 1% ~ (p=0.240 n=6)
LinkWithoutDebugCompiler 421.2m ± 6% 423.4m ± 7% ~ (p=0.699 n=6)
StdCmd 29.21 ± 2% 29.27 ± 1% ~ (p=0.937 n=6)
geomean 525.6m 522.3m -0.63%
│ parent.txt │ commit.txt │
│ user-sec/op │ user-sec/op vs base │
Template 728.1m ± 5% 686.9m ± 11% ~ (p=0.180 n=6)
Unicode 158.9m ± 12% 160.9m ± 8% ~ (p=0.937 n=6)
GoTypes 4.921 ± 4% 4.919 ± 2% ~ (p=0.937 n=6)
Compiler 502.4m ± 9% 505.3m ± 10% ~ (p=0.818 n=6)
SSA 37.77 ± 2% 37.06 ± 4% ~ (p=0.699 n=6)
Flate 751.3m ± 11% 759.7m ± 8% ~ (p=0.937 n=6)
GoParser 677.4m ± 5% 674.4m ± 8% ~ (p=0.394 n=6)
Reflect 1.888 ± 2% 1.901 ± 3% ~ (p=0.589 n=6)
Tar 772.1m ± 14% 789.7m ± 5% ~ (p=0.937 n=6)
XML 911.8m ± 9% 916.0m ± 12% ~ (p=0.937 n=6)
LinkCompiler 1.077 ± 4% 1.059 ± 5% ~ (p=0.310 n=6)
ExternalLinkCompiler 2.539 ± 2% 2.531 ± 3% ~ (p=1.000 n=6)
LinkWithoutDebugCompiler 489.7m ± 4% 485.3m ± 5% ~ (p=1.000 n=6)
geomean 1.192 1.187 -0.36%
│ parent.txt │ commit.txt │
│ text-bytes │ text-bytes vs base │
HelloSize 1.110Mi ± 0% 1.110Mi ± 0% ~ (p=1.000 n=6) ¹
CmdGoSize 14.14Mi ± 0% 14.14Mi ± 0% ~ (p=1.000 n=6) ¹
geomean 3.961Mi 3.961Mi +0.00%
¹ all samples are equal
│ parent.txt │ commit.txt │
│ data-bytes │ data-bytes vs base │
HelloSize 27.54Ki ± 0% 27.54Ki ± 0% ~ (p=1.000 n=6) ¹
CmdGoSize 431.4Ki ± 0% 431.4Ki ± 0% ~ (p=1.000 n=6) ¹
geomean 109.0Ki 109.0Ki +0.00%
¹ all samples are equal
│ parent.txt │ commit.txt │
│ bss-bytes │ bss-bytes vs base │
HelloSize 213.9Ki ± 0% 213.9Ki ± 0% ~ (p=1.000 n=6) ¹
CmdGoSize 32.27Mi ± 0% 32.27Mi ± 0% ~ (p=1.000 n=6) ¹
geomean 2.597Mi 2.597Mi +0.00%
¹ all samples are equal
│ parent.txt │ commit.txt │
│ exe-bytes │ exe-bytes vs base │
HelloSize 1.782Mi ± 0% 1.782Mi ± 0% ~ (p=1.000 n=6) ¹
CmdGoSize 21.29Mi ± 0% 21.29Mi ± 0% ~ (p=1.000 n=6) ¹
geomean 6.158Mi 6.158Mi +0.00%
¹ all samples are equal
Fixes #73132
Change-Id: Ice3ed672db28dbfd4bdc788019111d5d3092c5bb
Reviewed-on: https://go-review.googlesource.com/c/go/+/771500
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
3c05d2a519
commit
5af294bac7
3 changed files with 142 additions and 28 deletions
|
|
@ -47,12 +47,7 @@ func (e *escape) call(ks []hole, call ir.Node) {
|
|||
if fn := ir.StaticCalleeName(v); fn != nil {
|
||||
fns = []*ir.Name{fn}
|
||||
} else if name, ok := v.(*ir.Name); ok {
|
||||
orig := name.Canonical()
|
||||
if as := ir.FuncSingleAssignment(orig); as != nil {
|
||||
if callee := ir.StaticCalleeName(as.Y); callee != nil {
|
||||
fns = []*ir.Name{callee}
|
||||
}
|
||||
}
|
||||
fns = resolveAssignedCallees(ir.FuncAssignments(name.Canonical()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -429,6 +424,27 @@ func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole {
|
|||
return e.teeHole(tagKs...)
|
||||
}
|
||||
|
||||
// resolveAssignedCallees resolves all assignment RHS values to static
|
||||
// callee names, skipping zero-value assignments since nil panics on
|
||||
// call and can't cause escape.
|
||||
func resolveAssignedCallees(assigns []*ir.AssignStmt) []*ir.Name {
|
||||
fns := make([]*ir.Name, 0, len(assigns))
|
||||
for _, as := range assigns {
|
||||
if ir.IsZero(as.Y) {
|
||||
continue // zero value panics on call; skip
|
||||
}
|
||||
callee := ir.StaticCalleeName(as.Y)
|
||||
if callee == nil {
|
||||
return nil
|
||||
}
|
||||
if callee.Func != nil && callee.Func.Pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) != 0 {
|
||||
return nil
|
||||
}
|
||||
fns = append(fns, callee)
|
||||
}
|
||||
return fns
|
||||
}
|
||||
|
||||
func isEscapeNonString(fns []*ir.Name, fntype *types.Type) bool {
|
||||
return len(fns) == 1 &&
|
||||
fns[0].Sym().Pkg.Path == "internal/abi" &&
|
||||
|
|
|
|||
|
|
@ -1020,19 +1020,18 @@ func Reassigned(name *Name) bool {
|
|||
return Any(name.Curfn, do)
|
||||
}
|
||||
|
||||
// FuncSingleAssignment returns the sole OAS *AssignStmt that assigns a
|
||||
// non-zero value to name, if name is a func-typed local variable (PAUTO)
|
||||
// with exactly one such assignment. Zero-value assignments (nil, bare
|
||||
// declarations) are ignored since nil panics on call. Returns nil if the
|
||||
// variable is not PAUTO, not func-typed, address-taken, has multiple
|
||||
// non-zero assignments, or has any complex assignments (OAS2, ORANGE).
|
||||
// Assignments inside nested closures are accepted because this is only
|
||||
// used for escape analysis callee resolution: the only alternative value
|
||||
// is nil, which panics on call.
|
||||
// FuncAssignments returns all simple (OAS) assignments of non-zero
|
||||
// values to name, if name is a func-typed local variable (PAUTO).
|
||||
// Zero-value assignments (nil, bare declarations) are ignored since
|
||||
// nil panics on call. Returns nil if the variable is not PAUTO, not
|
||||
// func-typed, address-taken, has any complex assignment (OAS2, ORANGE),
|
||||
// or has too many assignments. Assignments inside nested closures are
|
||||
// accepted because this is only used for escape analysis callee
|
||||
// resolution: the only alternative value is nil, which panics on call.
|
||||
//
|
||||
// TODO: fold this into [ReassignOracle] so it can share the single
|
||||
// walk with StaticValue and Reassigned.
|
||||
func FuncSingleAssignment(name *Name) *AssignStmt {
|
||||
func FuncAssignments(name *Name) []*AssignStmt {
|
||||
if name.Class != PAUTO {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1043,12 +1042,9 @@ func FuncSingleAssignment(name *Name) *AssignStmt {
|
|||
if name.Type().Kind() != types.TFUNC {
|
||||
return nil
|
||||
}
|
||||
// Reject variables with non-zero defining assignments we can't
|
||||
// analyze (e.g., type switch case variables whose Defn is a
|
||||
// TypeSwitchGuard, not an AssignStmt).
|
||||
var found []*AssignStmt
|
||||
if name.Defn != nil {
|
||||
as, ok := name.Defn.(*AssignStmt)
|
||||
if !ok || !isNilAssign(as) {
|
||||
if _, ok := name.Defn.(*AssignStmt); !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -1061,8 +1057,6 @@ func FuncSingleAssignment(name *Name) *AssignStmt {
|
|||
return ok && n.Canonical() == name
|
||||
}
|
||||
|
||||
var found *AssignStmt
|
||||
|
||||
var do func(n Node) bool
|
||||
do = func(n Node) bool {
|
||||
switch n.Op() {
|
||||
|
|
@ -1072,11 +1066,7 @@ func FuncSingleAssignment(name *Name) *AssignStmt {
|
|||
if isNilAssign(as) {
|
||||
break
|
||||
}
|
||||
if found != nil {
|
||||
found = nil
|
||||
return true
|
||||
}
|
||||
found = as
|
||||
found = append(found, as)
|
||||
}
|
||||
case OAS2, OAS2FUNC, OAS2MAPR, OAS2DOTTYPE, OAS2RECV, OSELRECV2:
|
||||
as := n.(*AssignListStmt)
|
||||
|
|
|
|||
|
|
@ -282,3 +282,111 @@ func ClosureIndirectNilReassign() {
|
|||
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
|
||||
f(new(int)) // ERROR "new\(int\) does not escape"
|
||||
}
|
||||
|
||||
func ClosureIndirectMultiAssign(b bool) {
|
||||
var f func(p *int)
|
||||
if b {
|
||||
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
|
||||
} else {
|
||||
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
|
||||
}
|
||||
f(new(int)) // ERROR "new\(int\) does not escape"
|
||||
}
|
||||
|
||||
func ClosureIndirectMultiAssignNamed(b bool) {
|
||||
var f func(*int)
|
||||
if b {
|
||||
f = nopFunc
|
||||
} else {
|
||||
f = nopFunc
|
||||
}
|
||||
f(new(int)) // ERROR "new\(int\) does not escape"
|
||||
}
|
||||
|
||||
func ClosureIndirectMultiAssignResult(b bool) *int {
|
||||
var f func(p *int) *int
|
||||
if b {
|
||||
f = func(p *int) *int { return p } // ERROR "leaking param: p to result ~r0 level=0" "func literal does not escape"
|
||||
} else {
|
||||
f = func(p *int) *int { return p } // ERROR "leaking param: p to result ~r0 level=0" "func literal does not escape"
|
||||
}
|
||||
return f(new(int)) // ERROR "new\(int\) escapes to heap"
|
||||
}
|
||||
|
||||
func ClosureIndirectMultiAssignSafe(b bool) int {
|
||||
var f func(p *int) int
|
||||
if b {
|
||||
f = func(p *int) int { return *p } // ERROR "p does not escape" "func literal does not escape"
|
||||
} else {
|
||||
f = func(p *int) int { return 42 } // ERROR "p does not escape" "func literal does not escape"
|
||||
}
|
||||
return f(new(int)) // ERROR "new\(int\) does not escape"
|
||||
}
|
||||
|
||||
func ClosureIndirectTripleAssign(x int) {
|
||||
var f func(p *int)
|
||||
switch x {
|
||||
case 1:
|
||||
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
|
||||
case 2:
|
||||
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
|
||||
default:
|
||||
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
|
||||
}
|
||||
f(new(int)) // ERROR "new\(int\) does not escape"
|
||||
}
|
||||
|
||||
func ClosureIndirectReassignInit(b bool) {
|
||||
f := func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
|
||||
if b {
|
||||
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
|
||||
}
|
||||
f(new(int)) // ERROR "new\(int\) does not escape"
|
||||
}
|
||||
|
||||
func ClosureIndirectNestedMultiAssign(b bool) {
|
||||
var f func(p *int)
|
||||
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
|
||||
func() { // ERROR "func literal does not escape"
|
||||
f = func(p *int) {} // ERROR "p does not escape" "func literal escapes to heap"
|
||||
}()
|
||||
f(new(int)) // ERROR "new\(int\) does not escape"
|
||||
}
|
||||
|
||||
type myFloat struct{ v float64 }
|
||||
|
||||
func (f *myFloat) add(p *myFloat) *myFloat { // ERROR "leaking param: f to result ~r0 level=0" "p does not escape"
|
||||
f.v += p.v
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *myFloat) sub(p *myFloat) *myFloat { // ERROR "leaking param: f to result ~r0 level=0" "p does not escape"
|
||||
f.v -= p.v
|
||||
return f
|
||||
}
|
||||
|
||||
func ClosureIndirectMethodExpr(b bool) {
|
||||
var op func(*myFloat, *myFloat) *myFloat
|
||||
if b {
|
||||
op = (*myFloat).add
|
||||
} else {
|
||||
op = (*myFloat).sub
|
||||
}
|
||||
f := &myFloat{1.0} // ERROR "&myFloat{...} does not escape"
|
||||
g := &myFloat{2.0} // ERROR "&myFloat{...} does not escape"
|
||||
op(f, g)
|
||||
}
|
||||
|
||||
func ClosureIndirectMethodExprMixed(b bool) {
|
||||
var op func(*myFloat, *myFloat) *myFloat
|
||||
if b {
|
||||
op = (*myFloat).add
|
||||
} else {
|
||||
op = func(f, g *myFloat) *myFloat { // ERROR "f does not escape" "g does not escape" "func literal does not escape"
|
||||
return nil
|
||||
}
|
||||
}
|
||||
f := &myFloat{1.0} // ERROR "&myFloat{...} does not escape"
|
||||
g := &myFloat{2.0} // ERROR "&myFloat{...} does not escape"
|
||||
op(f, g)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue