]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: regalloc: drop values that aren't used until after a call
authorKeith Randall <khr@golang.org>
Wed, 12 Jul 2023 22:31:25 +0000 (15:31 -0700)
committerKeith Randall <khr@golang.org>
Mon, 26 Aug 2024 22:29:43 +0000 (22:29 +0000)
No point in keeping values in registers when their next use is after
a call, as we'd have to spill/restore them anyway.

cmd/go is 0.1% smaller.

Fixes #59297

Change-Id: I10ee761d0d23229f57de278f734c44d6a8dccd6c
Reviewed-on: https://go-review.googlesource.com/c/go/+/509255
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
src/cmd/compile/internal/ssa/regalloc.go
test/codegen/issue59297.go [new file with mode: 0644]

index 68f90e4a5032586d248f41cd9038151bfd672c2f..2771f3b9dde24a7c9b277d6b35e5547289ae6ba7 100644 (file)
@@ -121,6 +121,7 @@ import (
        "cmd/internal/sys"
        "fmt"
        "internal/buildcfg"
+       "math"
        "math/bits"
        "unsafe"
 )
@@ -210,7 +211,12 @@ func pickReg(r regMask) register {
 }
 
 type use struct {
-       dist int32    // distance from start of the block to a use of a value
+       // distance from start of the block to a use of a value
+       //   dist == 0                 used by first instruction in block
+       //   dist == len(b.Values)-1   used by last instruction in block
+       //   dist == len(b.Values)     used by block's control value
+       //   dist  > len(b.Values)     used by a subsequent block
+       dist int32
        pos  src.XPos // source position of the use
        next *use     // linked list of uses of a value in nondecreasing dist order
 }
@@ -314,6 +320,17 @@ type regAllocState struct {
 
        // whether to insert instructions that clobber dead registers at call sites
        doClobber bool
+
+       // For each instruction index in a basic block, the index of the next call
+       // at or after that instruction index.
+       // If there is no next call, returns maxInt32.
+       // nextCall for a call instruction points to itself.
+       // (Indexes and results are pre-regalloc.)
+       nextCall []int32
+
+       // Index of the instruction we're currently working on.
+       // Index is expressed in terms of the pre-regalloc b.Values list.
+       curIdx int
 }
 
 type endReg struct {
@@ -801,13 +818,27 @@ func (s *regAllocState) advanceUses(v *Value) {
                ai := &s.values[a.ID]
                r := ai.uses
                ai.uses = r.next
-               if r.next == nil {
-                       // Value is dead, free all registers that hold it.
+               if r.next == nil || (a.Op != OpSP && a.Op != OpSB && r.next.dist > s.nextCall[s.curIdx]) {
+                       // Value is dead (or is not used again until after a call), free all registers that hold it.
                        s.freeRegs(ai.regs)
                }
                r.next = s.freeUseRecords
                s.freeUseRecords = r
        }
+       s.dropIfUnused(v)
+}
+
+// Drop v from registers if it isn't used again, or its only uses are after
+// a call instruction.
+func (s *regAllocState) dropIfUnused(v *Value) {
+       if !s.values[v.ID].needReg {
+               return
+       }
+       vi := &s.values[v.ID]
+       r := vi.uses
+       if r == nil || (v.Op != OpSP && v.Op != OpSB && r.dist > s.nextCall[s.curIdx]) {
+               s.freeRegs(vi.regs)
+       }
 }
 
 // liveAfterCurrentInstruction reports whether v is live after
@@ -932,6 +963,10 @@ func (s *regAllocState) regalloc(f *Func) {
                                regValLiveSet.add(v.ID)
                        }
                }
+               if len(s.nextCall) < len(b.Values) {
+                       s.nextCall = append(s.nextCall, make([]int32, len(b.Values)-len(s.nextCall))...)
+               }
+               var nextCall int32 = math.MaxInt32
                for i := len(b.Values) - 1; i >= 0; i-- {
                        v := b.Values[i]
                        regValLiveSet.remove(v.ID)
@@ -939,6 +974,7 @@ func (s *regAllocState) regalloc(f *Func) {
                                // Remove v from the live set, but don't add
                                // any inputs. This is the state the len(b.Preds)>1
                                // case below desires; it wants to process phis specially.
+                               s.nextCall[i] = nextCall
                                continue
                        }
                        if opcodeTable[v.Op].call {
@@ -950,6 +986,7 @@ func (s *regAllocState) regalloc(f *Func) {
                                if s.sb != 0 && s.values[s.sb].uses != nil {
                                        regValLiveSet.add(s.sb)
                                }
+                               nextCall = int32(i)
                        }
                        for _, a := range v.Args {
                                if !s.values[a.ID].needReg {
@@ -958,6 +995,7 @@ func (s *regAllocState) regalloc(f *Func) {
                                s.addUse(a.ID, int32(i), v.Pos)
                                regValLiveSet.add(a.ID)
                        }
+                       s.nextCall[i] = nextCall
                }
                if s.f.pass.debug > regDebug {
                        fmt.Printf("use distances for %s\n", b)
@@ -1222,6 +1260,12 @@ func (s *regAllocState) regalloc(f *Func) {
                        }
                }
 
+               // Drop phis from registers if they immediately go dead.
+               for i, v := range phis {
+                       s.curIdx = i
+                       s.dropIfUnused(v)
+               }
+
                // Allocate space to record the desired registers for each value.
                if l := len(oldSched); cap(dinfo) < l {
                        dinfo = make([]dentry, l)
@@ -1306,6 +1350,7 @@ func (s *regAllocState) regalloc(f *Func) {
 
                // Process all the non-phi values.
                for idx, v := range oldSched {
+                       s.curIdx = nphi + idx
                        tmpReg := noRegister
                        if s.f.pass.debug > regDebug {
                                fmt.Printf("  processing %s\n", v.LongString())
@@ -1761,6 +1806,7 @@ func (s *regAllocState) regalloc(f *Func) {
                                v.SetArg(i, a) // use register version of arguments
                        }
                        b.Values = append(b.Values, v)
+                       s.dropIfUnused(v)
                }
 
                // Copy the control values - we need this so we can reduce the
diff --git a/test/codegen/issue59297.go b/test/codegen/issue59297.go
new file mode 100644 (file)
index 0000000..1703a1a
--- /dev/null
@@ -0,0 +1,17 @@
+// asmcheck
+
+// Copyright 2023 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 codegen
+
+func f(x, y int, p *int) {
+       // amd64:`MOVQ\sAX, BX`
+       h(8, x)
+       *p = y
+}
+
+//go:noinline
+func h(a, b int) {
+}