go/ssa: fix regression in φ-elimination
https://go-review.googlesource.com/37157 introduced a bug that caused
some live φ-nodes to be removed from the CFG. The cause was that
reachability traversal considered edges only among "new" φ-nodes
(those introduced during SSA renaming) but not existing φ-nodes from
&& and || expressions. The fix is to mark existing phis, and thus
other phis reachable from them, as live. We also clear the Phi.block
field when eliminating a φ-node.
Also, during reachability, we treat DebugRef instructions as roots
like any other non-Phi instruction. This eliminates a related known
bug whereby the operand of a DebugRef may be a dead φ.
This change also adds a sanity check that all operands of an SSA value
that are themselves instructions must belong to a block. The sanity
check would fail 7 times on the standard library without the fix.
Fixes golang/go#19622
Change-Id: If3a897a6a593a17bc3f0f8228d1edf483be7a3d0
Reviewed-on: https://go-review.googlesource.com/45832
Run-TryBot: Dominik Honnef <dominik@honnef.co>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Dominik Honnef <dominik@honnef.co>
diff --git a/go/ssa/lift.go b/go/ssa/lift.go
index 00c8a72..048e9b0 100644
--- a/go/ssa/lift.go
+++ b/go/ssa/lift.go
@@ -214,7 +214,7 @@
rename(fn.Blocks[0], renaming, newPhis)
// Eliminate dead φ-nodes.
- removeDeadPhis(newPhis)
+ removeDeadPhis(fn.Blocks, newPhis)
// Prepend remaining live φ-nodes to each block.
for _, b := range fn.Blocks {
@@ -269,8 +269,14 @@
// removeDeadPhis removes φ-nodes not transitively needed by a
// non-Phi, non-DebugRef instruction.
-func removeDeadPhis(newPhis newPhiMap) {
- // First pass: compute reachability from non-Phi/DebugRef instructions.
+func removeDeadPhis(blocks []*BasicBlock, newPhis newPhiMap) {
+ // First pass: find the set of "live" φ-nodes: those reachable
+ // from some non-Phi instruction.
+ //
+ // We compute reachability in reverse, starting from each φ,
+ // rather than forwards, starting from each live non-Phi
+ // instruction, because this way visits much less of the
+ // Value graph.
livePhis := make(map[*Phi]bool)
for _, npList := range newPhis {
for _, np := range npList {
@@ -281,6 +287,14 @@
}
}
+ // Existing φ-nodes due to && and || operators
+ // are all considered live (see Go issue 19622).
+ for _, b := range blocks {
+ for _, phi := range b.phis() {
+ markLivePhi(livePhis, phi.(*Phi))
+ }
+ }
+
// Second pass: eliminate unused phis from newPhis.
for block, npList := range newPhis {
j := 0
@@ -295,9 +309,7 @@
*refs = removeInstr(*refs, np.phi)
}
}
- // This may leave DebugRef instructions referring to
- // Phis that aren't in the control flow graph.
- // TODO(adonovan): we should delete them.
+ np.phi.block = nil
}
}
newPhis[block] = npList[:j]
@@ -318,14 +330,11 @@
}
// phiHasDirectReferrer reports whether phi is directly referred to by
-// a non-Phi, non-DebugRef instruction. Such instructions are the
+// a non-Phi instruction. Such instructions are the
// roots of the liveness traversal.
func phiHasDirectReferrer(phi *Phi) bool {
for _, instr := range *phi.Referrers() {
- switch instr.(type) {
- case *Phi, *DebugRef:
- // ignore
- default:
+ if _, ok := instr.(*Phi); !ok {
return true
}
}
diff --git a/go/ssa/print.go b/go/ssa/print.go
index 0c1b270..3333ba4 100644
--- a/go/ssa/print.go
+++ b/go/ssa/print.go
@@ -89,8 +89,12 @@
b.WriteString(", ")
}
// Be robust against malformed CFG.
+ if v.block == nil {
+ b.WriteString("??")
+ continue
+ }
block := -1
- if v.block != nil && i < len(v.block.Preds) {
+ if i < len(v.block.Preds) {
block = v.block.Preds[i].Index
}
fmt.Fprintf(&b, "%d: ", block)
diff --git a/go/ssa/sanity.go b/go/ssa/sanity.go
index 638a2c4..9e49e3b 100644
--- a/go/ssa/sanity.go
+++ b/go/ssa/sanity.go
@@ -351,7 +351,10 @@
// Check that Operands that are also Instructions belong to same function.
// TODO(adonovan): also check their block dominates block b.
if val, ok := val.(Instruction); ok {
- if val.Parent() != s.fn {
+ if val.Block() == nil {
+ val.String()
+ s.errorf("operand %d of %s is an instruction (%s) that belongs to no block", i, instr, val)
+ } else if val.Parent() != s.fn {
s.errorf("operand %d of %s is an instruction (%s) from function %s", i, instr, val, val.Parent())
}
}