cmd/compile: teach deadstore about moves

Moves that read from read-only memory can't be reading the results
of a previous store. These are often generated by constant struct literals.
Moves whose results aren't needed because that memory is immediately
overwritten, are not needed.

Saves a few bytes of generated code (~<0.1%).

Change-Id: I8dab6d1b9c066d6b623eae8b8fe31a51dd3de006
Reviewed-on: https://go-review.googlesource.com/c/go/+/771780
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: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Reviewed-by: Jakub Ciolek <jakub@ciolek.dev>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Keith Randall 2026-04-28 14:27:19 -07:00
parent 4aa6dad54e
commit da6a4cd70a
2 changed files with 48 additions and 7 deletions

View file

@ -50,7 +50,18 @@ func dse(f *Func) {
for _, a := range v.Args {
if a.Block == b && a.Type.IsMemory() {
storeUse.add(a.ID)
if v.Op != OpStore && v.Op != OpZero && v.Op != OpVarDef {
switch v.Op {
case OpStore, OpZero, OpVarDef:
// These ops never read from their memory input.
case OpMove:
// This op reads from its memory argument, but
// we can treat it as not doing so if we know
// the read is from read-only memory.
if v.Args[1].Op == OpAddr && symIsRO(auxToSym(v.Args[1].Aux)) {
break
}
fallthrough
default:
// CALL, DUFFCOPY, etc. are both
// reads and writes.
loadUse.add(a.ID)
@ -111,7 +122,7 @@ func dse(f *Func) {
shadowed.clear()
shadowedRanges = shadowedRanges[:0]
}
if v.Op == OpStore || v.Op == OpZero {
if v.Op == OpStore || v.Op == OpZero || v.Op == OpMove {
ptr := v.Args[0]
var off int64
for ptr.Op == OpOffPtr { // Walk to base pointer
@ -119,7 +130,7 @@ func dse(f *Func) {
ptr = ptr.Args[0]
}
var sz int64
if v.Op == OpStore {
if v.Op == OpStore || v.Op == OpMove {
sz = v.Aux.(*types.Type).Size()
} else { // OpZero
sz = v.AuxInt
@ -137,13 +148,14 @@ func dse(f *Func) {
}
if si != nil && si.contains(off, off+sz) {
// Modify the store/zero into a copy of the memory state,
// Modify the store/zero/move into a copy of the memory state,
// effectively eliding the store operation.
if v.Op == OpStore {
// store addr value mem
if v.Op == OpStore || v.Op == OpMove {
// Store addr value mem
// or Move dst src mem
v.SetArgs1(v.Args[2])
} else {
// zero addr mem
// Zero addr mem
v.SetArgs1(v.Args[1])
}
v.Aux = nil

29
test/codegen/deadstore.go Normal file
View file

@ -0,0 +1,29 @@
// asmcheck
// Copyright 2026 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
type S struct {
a, b, c, d, e int
}
func f1(s *S) {
// amd64:-`MOVUPS`
// arm64:-`STP` -`MOVD`
*s = S{}
*s = S{a: 3, b: 4, c: 5, d: 6, e: 7}
}
func f2(s *S) {
// amd64:-`MOVUPS`
// arm64:-`MOVD` -`FSTPQ`
*s = S{a: 1, b: 2, c: 3, d: 4, e: 5}
s.a = 3
s.b = 4
s.c = 5
s.d = 6
s.e = 7
}