1
0
mirror of https://github.com/golang/go synced 2024-11-18 18:44:42 -07:00

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>
This commit is contained in:
Alan Donovan 2017-06-14 21:08:52 -04:00
parent 365911de24
commit 6d30ceaab7
3 changed files with 29 additions and 13 deletions

View File

@ -214,7 +214,7 @@ func lift(fn *Function) {
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 @@ func lift(fn *Function) {
// 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 @@ func removeDeadPhis(newPhis newPhiMap) {
}
}
// 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 @@ func removeDeadPhis(newPhis newPhiMap) {
*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 @@ func markLivePhi(livePhis map[*Phi]bool, phi *Phi) {
}
// 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
}
}

View File

@ -89,8 +89,12 @@ func (v *Phi) String() string {
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)

View File

@ -351,7 +351,10 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) {
// 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())
}
}