cmd/compile: invalidate zero-use values during rewrite

This helps remove uses that aren't needed any more.
That in turn helps other rules with Uses==1 conditions fire.

Update #39918

Change-Id: I68635b675472f1d59e59604e4d34b949a0016533
Reviewed-on: https://go-review.googlesource.com/c/go/+/249463
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
diff --git a/src/cmd/compile/internal/ssa/decompose.go b/src/cmd/compile/internal/ssa/decompose.go
index 6e72e38..ab27ba8 100644
--- a/src/cmd/compile/internal/ssa/decompose.go
+++ b/src/cmd/compile/internal/ssa/decompose.go
@@ -23,9 +23,11 @@
 	}
 
 	// Decompose other values
-	applyRewrite(f, rewriteBlockdec, rewriteValuedec)
+	// Note: deadcode is false because we need to keep the original
+	// values around so the name component resolution below can still work.
+	applyRewrite(f, rewriteBlockdec, rewriteValuedec, leaveDeadValues)
 	if f.Config.RegSize == 4 {
-		applyRewrite(f, rewriteBlockdec64, rewriteValuedec64)
+		applyRewrite(f, rewriteBlockdec64, rewriteValuedec64, leaveDeadValues)
 	}
 
 	// Split up named values into their components.
@@ -215,7 +217,7 @@
 }
 
 func decomposeArgs(f *Func) {
-	applyRewrite(f, rewriteBlockdecArgs, rewriteValuedecArgs)
+	applyRewrite(f, rewriteBlockdecArgs, rewriteValuedecArgs, removeDeadValues)
 }
 
 func decomposeUser(f *Func) {
diff --git a/src/cmd/compile/internal/ssa/lower.go b/src/cmd/compile/internal/ssa/lower.go
index ab0fa80..f332b2e 100644
--- a/src/cmd/compile/internal/ssa/lower.go
+++ b/src/cmd/compile/internal/ssa/lower.go
@@ -7,7 +7,7 @@
 // convert to machine-dependent ops
 func lower(f *Func) {
 	// repeat rewrites until we find no more rewrites
-	applyRewrite(f, f.Config.lowerBlock, f.Config.lowerValue)
+	applyRewrite(f, f.Config.lowerBlock, f.Config.lowerValue, removeDeadValues)
 }
 
 // checkLower checks for unlowered opcodes and fails if we find one.
diff --git a/src/cmd/compile/internal/ssa/opt.go b/src/cmd/compile/internal/ssa/opt.go
index 6e91fd7..128e614 100644
--- a/src/cmd/compile/internal/ssa/opt.go
+++ b/src/cmd/compile/internal/ssa/opt.go
@@ -6,5 +6,5 @@
 
 // machine-independent optimization
 func opt(f *Func) {
-	applyRewrite(f, rewriteBlockgeneric, rewriteValuegeneric)
+	applyRewrite(f, rewriteBlockgeneric, rewriteValuegeneric, removeDeadValues)
 }
diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go
index fb35691..3df9ad2 100644
--- a/src/cmd/compile/internal/ssa/rewrite.go
+++ b/src/cmd/compile/internal/ssa/rewrite.go
@@ -20,7 +20,15 @@
 	"path/filepath"
 )
 
-func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) {
+type deadValueChoice bool
+
+const (
+	leaveDeadValues  deadValueChoice = false
+	removeDeadValues                 = true
+)
+
+// deadcode indicates that rewrite should try to remove any values that become dead.
+func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter, deadcode deadValueChoice) {
 	// repeat rewrites until we find no more rewrites
 	pendingLines := f.cachedLineStarts // Holds statement boundaries that need to be moved to a new value/block
 	pendingLines.clear()
@@ -56,6 +64,18 @@
 					*v0 = *v
 					v0.Args = append([]*Value{}, v.Args...) // make a new copy, not aliasing
 				}
+				if v.Uses == 0 && v.removeable() {
+					if v.Op != OpInvalid && deadcode == removeDeadValues {
+						// Reset any values that are now unused, so that we decrement
+						// the use count of all of its arguments.
+						// Not quite a deadcode pass, because it does not handle cycles.
+						// But it should help Uses==1 rules to fire.
+						v.reset(OpInvalid)
+						change = true
+					}
+					// No point rewriting values which aren't used.
+					continue
+				}
 
 				vchange := phielimValue(v)
 				if vchange && debug > 1 {
diff --git a/src/cmd/compile/internal/ssa/softfloat.go b/src/cmd/compile/internal/ssa/softfloat.go
index 4b578b1..8db4334 100644
--- a/src/cmd/compile/internal/ssa/softfloat.go
+++ b/src/cmd/compile/internal/ssa/softfloat.go
@@ -72,7 +72,7 @@
 	if newInt64 && f.Config.RegSize == 4 {
 		// On 32bit arch, decompose Uint64 introduced in the switch above.
 		decomposeBuiltIn(f)
-		applyRewrite(f, rewriteBlockdec64, rewriteValuedec64)
+		applyRewrite(f, rewriteBlockdec64, rewriteValuedec64, removeDeadValues)
 	}
 
 }
diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go
index 7ead0ff..7fc3377 100644
--- a/src/cmd/compile/internal/ssa/value.go
+++ b/src/cmd/compile/internal/ssa/value.go
@@ -460,3 +460,23 @@
 	return v.Op == OpVarDef || v.Op == OpVarKill || v.Op == OpVarLive || v.Op == OpPhi ||
 		(v.Op == OpFwdRef || v.Op == OpCopy) && v.Type == types.TypeMem
 }
+
+// removeable reports whether the value v can be removed from the SSA graph entirely
+// if its use count drops to 0.
+func (v *Value) removeable() bool {
+	if v.Type.IsVoid() {
+		// Void ops, like nil pointer checks, must stay.
+		return false
+	}
+	if v.Type.IsMemory() {
+		// All memory ops aren't needed here, but we do need
+		// to keep calls at least (because they might have
+		// syncronization operations we can't see).
+		return false
+	}
+	if v.Op.HasSideEffects() {
+		// These are mostly synchronization operations.
+		return false
+	}
+	return true
+}