1
0
mirror of https://github.com/golang/go synced 2024-11-26 07:27:59 -07:00

cmd/compile: fix and improve alias detection

"aliased" is the function responsible for detecting whether we can
turn "a, b = x, y" into just "a = x; b = y", or we need to pre-compute
y and save it in a temporary variable because it might depend on a.

It currently has two issues:

1. It suboptimally treats assignments to blank as writes to heap
   memory. Users generally won't write "_, b = x, y" directly, but it
   comes up a lot in generated code within the compiler.

   This CL changes it to ignore blank assignments.

2. When deciding whether the assigned variable might be referenced by
   pointers, it mistakenly checks Class() and Name.Addrtaken() on "n"
   (the *value* expression being assigned) rather than "a" (the
   destination expression).

   It doesn't appear to result in correctness issues (i.e.,
   incorrectly reporting no aliasing when there is potential aliasing),
   due to all the (overly conservative) rewrite passes before code
   reaches here. But it generates unnecessary code and could have
   correctness issues if we improve those other passes to be more
   aggressive.

   This CL fixes the misuse of "n" for "a" by renaming the variables
   to "r" and "l", respectively, to make their meaning clearer.

Improving these two cases shaves 4.6kB of text from cmd/go, and 93kB
from k8s.io/kubernetes/cmd/kubelet:

       text	   data	    bss	    dec	    hex	filename
    9732136	 290072	 231552	10253760	 9c75c0	go.before
    9727542	 290072	 231552	10249166	 9c63ce	go.after
    97977637	1007051	 301344	99286032	5eafc10	kubelet.before
    97884549	1007051	 301344	99192944	5e99070	kubelet.after

While here, this CL also collapses "memwrite" and "varwrite" into a
single variable. Logically, they're detecting the same thing: are we
assigning to a memory location that a pointer might alias. There's no
need for two variables.

Updates #6853.
Updates #23017.

Change-Id: I5a307b8e20bcd2196e85c55eb025d3f01e303008
Reviewed-on: https://go-review.googlesource.com/c/go/+/261677
Trust: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Matthew Dempsky 2020-10-12 14:02:36 -07:00
parent c9211577eb
commit 85bb4294c0

View File

@ -2157,7 +2157,7 @@ func reorder3(all []*Node) []*Node {
// The result of reorder3save MUST be assigned back to n, e.g. // The result of reorder3save MUST be assigned back to n, e.g.
// n.Left = reorder3save(n.Left, all, i, early) // n.Left = reorder3save(n.Left, all, i, early)
func reorder3save(n *Node, all []*Node, i int, early *[]*Node) *Node { func reorder3save(n *Node, all []*Node, i int, early *[]*Node) *Node {
if !aliased(n, all, i) { if !aliased(n, all[:i]) {
return n return n
} }
@ -2189,73 +2189,75 @@ func outervalue(n *Node) *Node {
} }
} }
// Is it possible that the computation of n might be // Is it possible that the computation of r might be
// affected by writes in as up to but not including the ith element? // affected by assignments in all?
func aliased(n *Node, all []*Node, i int) bool { func aliased(r *Node, all []*Node) bool {
if n == nil { if r == nil {
return false return false
} }
// Treat all fields of a struct as referring to the whole struct. // Treat all fields of a struct as referring to the whole struct.
// We could do better but we would have to keep track of the fields. // We could do better but we would have to keep track of the fields.
for n.Op == ODOT { for r.Op == ODOT {
n = n.Left r = r.Left
} }
// Look for obvious aliasing: a variable being assigned // Look for obvious aliasing: a variable being assigned
// during the all list and appearing in n. // during the all list and appearing in n.
// Also record whether there are any writes to main memory. // Also record whether there are any writes to addressable
// Also record whether there are any writes to variables // memory (either main memory or variables whose addresses
// whose addresses have been taken. // have been taken).
memwrite := false memwrite := false
varwrite := false for _, as := range all {
for _, an := range all[:i] { // We can ignore assignments to blank.
a := outervalue(an.Left) if as.Left.isBlank() {
continue
for a.Op == ODOT {
a = a.Left
} }
if a.Op != ONAME { l := outervalue(as.Left)
if l.Op != ONAME {
memwrite = true memwrite = true
continue continue
} }
switch n.Class() { switch l.Class() {
default: default:
varwrite = true Fatalf("unexpected class: %v, %v", l, l.Class())
case PAUTOHEAP, PEXTERN:
memwrite = true
continue continue
case PAUTO, PPARAM, PPARAMOUT: case PAUTO, PPARAM, PPARAMOUT:
if n.Name.Addrtaken() { if l.Name.Addrtaken() {
varwrite = true memwrite = true
continue continue
} }
if vmatch2(a, n) { if vmatch2(l, r) {
// Direct hit. // Direct hit: l appears in r.
return true return true
} }
} }
} }
// The variables being written do not appear in n. // The variables being written do not appear in r.
// However, n might refer to computed addresses // However, r might refer to computed addresses
// that are being written. // that are being written.
// If no computed addresses are affected by the writes, no aliasing. // If no computed addresses are affected by the writes, no aliasing.
if !memwrite && !varwrite { if !memwrite {
return false return false
} }
// If n does not refer to computed addresses // If r does not refer to computed addresses
// (that is, if n only refers to variables whose addresses // (that is, if r only refers to variables whose addresses
// have not been taken), no aliasing. // have not been taken), no aliasing.
if varexpr(n) { if varexpr(r) {
return false return false
} }
// Otherwise, both the writes and n refer to computed memory addresses. // Otherwise, both the writes and r refer to computed memory addresses.
// Assume that they might conflict. // Assume that they might conflict.
return true return true
} }