1
0
mirror of https://github.com/golang/go synced 2024-11-17 16:24:42 -07:00

cmd/compile: add new escape analysis implementation

This CL adds a new escape analysis implementation, which can be
enabled through the -newescape compiler flag.

This implementation focuses on simplicity, but in the process ends up
using less memory, speeding up some compile-times, fixing memory
corruption issues, and overall significantly improving escape analysis
results.

Updates #23109.

Change-Id: I6176d9a7ae9d80adb0208d4112b8a1e1f4c9143a
Reviewed-on: https://go-review.googlesource.com/c/go/+/170322
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Matthew Dempsky 2019-04-02 10:40:12 -07:00
parent d91f7e6637
commit 97c4ad4327
3 changed files with 1415 additions and 15 deletions

View File

@ -41,8 +41,16 @@ import (
// not escape, then new(T) can be rewritten into a stack allocation. // not escape, then new(T) can be rewritten into a stack allocation.
// The same is true of slice literals. // The same is true of slice literals.
// If newescape is true, then escape.go drives escape analysis instead
// of esc.go.
var newescape bool
func escapes(all []*Node) { func escapes(all []*Node) {
visitBottomUp(all, escAnalyze) esc := escAnalyze
if newescape {
esc = escapeFuncs
}
visitBottomUp(all, esc)
} }
const ( const (
@ -393,7 +401,7 @@ func escAnalyze(all []*Node, recursive bool) {
// for all top level functions, tag the typenodes corresponding to the param nodes // for all top level functions, tag the typenodes corresponding to the param nodes
for _, n := range all { for _, n := range all {
if n.Op == ODCLFUNC { if n.Op == ODCLFUNC {
e.esctag(n) esctag(n)
} }
} }
@ -516,7 +524,7 @@ func (e *EscState) esclist(l Nodes, parent *Node) {
} }
} }
func (e *EscState) isSliceSelfAssign(dst, src *Node) bool { func isSliceSelfAssign(dst, src *Node) bool {
// Detect the following special case. // Detect the following special case.
// //
// func (b *Buffer) Foo() { // func (b *Buffer) Foo() {
@ -566,8 +574,8 @@ func (e *EscState) isSliceSelfAssign(dst, src *Node) bool {
// isSelfAssign reports whether assignment from src to dst can // isSelfAssign reports whether assignment from src to dst can
// be ignored by the escape analysis as it's effectively a self-assignment. // be ignored by the escape analysis as it's effectively a self-assignment.
func (e *EscState) isSelfAssign(dst, src *Node) bool { func isSelfAssign(dst, src *Node) bool {
if e.isSliceSelfAssign(dst, src) { if isSliceSelfAssign(dst, src) {
return true return true
} }
@ -589,7 +597,7 @@ func (e *EscState) isSelfAssign(dst, src *Node) bool {
case ODOT, ODOTPTR: case ODOT, ODOTPTR:
// Safe trailing accessors that are permitted to differ. // Safe trailing accessors that are permitted to differ.
case OINDEX: case OINDEX:
if e.mayAffectMemory(dst.Right) || e.mayAffectMemory(src.Right) { if mayAffectMemory(dst.Right) || mayAffectMemory(src.Right) {
return false return false
} }
default: default:
@ -602,7 +610,7 @@ func (e *EscState) isSelfAssign(dst, src *Node) bool {
// mayAffectMemory reports whether n evaluation may affect program memory state. // mayAffectMemory reports whether n evaluation may affect program memory state.
// If expression can't affect it, then it can be safely ignored by the escape analysis. // If expression can't affect it, then it can be safely ignored by the escape analysis.
func (e *EscState) mayAffectMemory(n *Node) bool { func mayAffectMemory(n *Node) bool {
// We may want to use "memory safe" black list instead of general // We may want to use "memory safe" black list instead of general
// "side-effect free", which can include all calls and other ops // "side-effect free", which can include all calls and other ops
// that can affect allocate or change global state. // that can affect allocate or change global state.
@ -616,18 +624,26 @@ func (e *EscState) mayAffectMemory(n *Node) bool {
// Left+Right group. // Left+Right group.
case OINDEX, OADD, OSUB, OOR, OXOR, OMUL, OLSH, ORSH, OAND, OANDNOT, ODIV, OMOD: case OINDEX, OADD, OSUB, OOR, OXOR, OMUL, OLSH, ORSH, OAND, OANDNOT, ODIV, OMOD:
return e.mayAffectMemory(n.Left) || e.mayAffectMemory(n.Right) return mayAffectMemory(n.Left) || mayAffectMemory(n.Right)
// Left group. // Left group.
case ODOT, ODOTPTR, ODEREF, OCONVNOP, OCONV, OLEN, OCAP, case ODOT, ODOTPTR, ODEREF, OCONVNOP, OCONV, OLEN, OCAP,
ONOT, OBITNOT, OPLUS, ONEG, OALIGNOF, OOFFSETOF, OSIZEOF: ONOT, OBITNOT, OPLUS, ONEG, OALIGNOF, OOFFSETOF, OSIZEOF:
return e.mayAffectMemory(n.Left) return mayAffectMemory(n.Left)
default: default:
return true return true
} }
} }
func mustHeapAlloc(n *Node) bool {
// TODO(mdempsky): Cleanup this mess.
return n.Type != nil &&
(n.Type.Width > maxStackVarSize ||
(n.Op == ONEW || n.Op == OPTRLIT) && n.Type.Elem().Width >= maxImplicitStackVarSize ||
n.Op == OMAKESLICE && !isSmallMakeSlice(n))
}
func (e *EscState) esc(n *Node, parent *Node) { func (e *EscState) esc(n *Node, parent *Node) {
if n == nil { if n == nil {
return return
@ -658,10 +674,7 @@ func (e *EscState) esc(n *Node, parent *Node) {
// Big stuff and non-constant-sized stuff escapes unconditionally. // Big stuff and non-constant-sized stuff escapes unconditionally.
// "Big" conditions that were scattered around in walk have been // "Big" conditions that were scattered around in walk have been
// gathered here. // gathered here.
if n.Esc != EscHeap && n.Type != nil && if n.Esc != EscHeap && mustHeapAlloc(n) {
(n.Type.Width > maxStackVarSize ||
(n.Op == ONEW || n.Op == OPTRLIT) && n.Type.Elem().Width >= maxImplicitStackVarSize ||
n.Op == OMAKESLICE && !isSmallMakeSlice(n)) {
// isSmallMakeSlice returns false for non-constant len/cap. // isSmallMakeSlice returns false for non-constant len/cap.
// If that's the case, print a more accurate escape reason. // If that's the case, print a more accurate escape reason.
var msgVerb, escapeMsg string var msgVerb, escapeMsg string
@ -756,7 +769,7 @@ opSwitch:
case OAS, OASOP: case OAS, OASOP:
// Filter out some no-op assignments for escape analysis. // Filter out some no-op assignments for escape analysis.
if e.isSelfAssign(n.Left, n.Right) { if isSelfAssign(n.Left, n.Right) {
if Debug['m'] != 0 { if Debug['m'] != 0 {
Warnl(n.Pos, "%v ignoring self-assignment in %S", e.curfnSym(n), n) Warnl(n.Pos, "%v ignoring self-assignment in %S", e.curfnSym(n), n)
} }
@ -2182,7 +2195,7 @@ const unsafeUintptrTag = "unsafe-uintptr"
// marked go:uintptrescapes. // marked go:uintptrescapes.
const uintptrEscapesTag = "uintptr-escapes" const uintptrEscapesTag = "uintptr-escapes"
func (e *EscState) esctag(fn *Node) { func esctag(fn *Node) {
fn.Esc = EscFuncTagged fn.Esc = EscFuncTagged
name := func(s *types.Sym, narg int) string { name := func(s *types.Sym, narg int) string {

File diff suppressed because it is too large Load Diff

View File

@ -253,6 +253,7 @@ func Main(archInit func(*Arch)) {
flag.StringVar(&blockprofile, "blockprofile", "", "write block profile to `file`") flag.StringVar(&blockprofile, "blockprofile", "", "write block profile to `file`")
flag.StringVar(&mutexprofile, "mutexprofile", "", "write mutex profile to `file`") flag.StringVar(&mutexprofile, "mutexprofile", "", "write mutex profile to `file`")
flag.StringVar(&benchfile, "bench", "", "append benchmark times to `file`") flag.StringVar(&benchfile, "bench", "", "append benchmark times to `file`")
flag.BoolVar(&newescape, "newescape", false, "enable new escape analysis")
objabi.Flagparse(usage) objabi.Flagparse(usage)
// Record flags that affect the build result. (And don't // Record flags that affect the build result. (And don't