Commit 9b048527 authored by Josh Bleecher Snyder's avatar Josh Bleecher Snyder

[dev.ssa] cmd/compile/ssa: handle nested dead blocks

removePredecessor can change which blocks are live.
However, it cannot remove dead blocks from the function's
slice of blocks because removePredecessor may have been
called from within a function doing a walk of the blocks.

CL 11879 did not handle this correctly and broke the build.

To fix this, mark the block as dead but leave its actual
removal for a deadcode pass. Blocks that are dead must have
no successors, predecessors, values, or control values,
so they will generally be ignored by other passes.
To be safe, we add a deadcode pass after the opt pass,
which is the only other pass that calls removePredecessor.

Two alternatives that I considered and discarded:

(1) Make all call sites aware of the fact that removePrecessor
might make arbitrary changes to the list of blocks. This
will needlessly complicate callers.

(2) Handle the things that can go wrong in practice when
we encounter a dead-but-not-removed block. CL 11930 takes
this approach (and the tests are stolen from that CL).
However, this is just patching over the problem.

Change-Id: Icf0687b0a8148ce5e96b2988b668804411b05bd8
Reviewed-on: https://go-review.googlesource.com/12004Reviewed-by: 's avatarTodd Neal <todd@tneal.org>
Reviewed-by: 's avatarMichael Matloob <michaelmatloob@gmail.com>
parent 9201c86b
......@@ -59,6 +59,19 @@ func checkFunc(f *Func) {
if !b.Control.Type.IsMemory() {
f.Fatalf("exit block %s has non-memory control value %s", b, b.Control.LongString())
}
case BlockDead:
if len(b.Succs) != 0 {
f.Fatalf("dead block %s has successors", b)
}
if len(b.Preds) != 0 {
f.Fatalf("dead block %s has predecessors", b)
}
if len(b.Values) != 0 {
f.Fatalf("dead block %s has values", b)
}
if b.Control != nil {
f.Fatalf("dead block %s has a control value", b)
}
case BlockPlain:
if len(b.Succs) != 1 {
f.Fatalf("plain block %s len(Succs)==%d, want 1", b, len(b.Succs))
......
......@@ -51,6 +51,7 @@ var passes = [...]pass{
{"phielim", phielim},
{"copyelim", copyelim},
{"opt", opt},
{"opt deadcode", deadcode}, // remove any blocks orphaned during opt
{"generic cse", cse},
{"nilcheckelim", nilcheckelim},
{"generic deadcode", deadcode},
......
......@@ -96,7 +96,7 @@ func deadcode(f *Func) {
// TODO: save dead Values and Blocks for reuse? Or should we just let GC handle it?
}
// There was an edge b->c. It has been removed from b's successors.
// There was an edge b->c. c has been removed from b's successors.
// Fix up c to handle that fact.
func (f *Func) removePredecessor(b, c *Block) {
work := [][2]*Block{{b, c}}
......@@ -105,8 +105,6 @@ func (f *Func) removePredecessor(b, c *Block) {
b, c := work[0][0], work[0][1]
work = work[1:]
n := len(c.Preds) - 1
// find index of b in c's predecessor list
var i int
for j, p := range c.Preds {
......@@ -116,6 +114,7 @@ func (f *Func) removePredecessor(b, c *Block) {
}
}
n := len(c.Preds) - 1
c.Preds[i] = c.Preds[n]
c.Preds[n] = nil // aid GC
c.Preds = c.Preds[:n]
......@@ -143,6 +142,9 @@ func (f *Func) removePredecessor(b, c *Block) {
for _, succ := range c.Succs {
work = append(work, [2]*Block{c, succ})
}
c.Succs = nil
c.Kind = BlockDead
c.Control = nil
}
}
}
......@@ -93,3 +93,42 @@ func TestNeverTaken(t *testing.T) {
}
}
func TestNestedDeadBlocks(t *testing.T) {
c := NewConfig("amd64", DummyFrontend{t})
fun := Fun(c, "entry",
Bloc("entry",
Valu("mem", OpArg, TypeMem, 0, ".mem"),
Valu("cond", OpConst, TypeBool, 0, false),
If("cond", "b2", "b4")),
Bloc("b2",
If("cond", "b3", "b4")),
Bloc("b3",
If("cond", "b3", "b4")),
Bloc("b4",
If("cond", "b3", "exit")),
Bloc("exit",
Exit("mem")))
CheckFunc(fun.f)
Opt(fun.f)
CheckFunc(fun.f)
Deadcode(fun.f)
CheckFunc(fun.f)
if fun.blocks["entry"].Kind != BlockPlain {
t.Errorf("if(false) not simplified")
}
for _, b := range fun.f.Blocks {
if b == fun.blocks["b2"] {
t.Errorf("b2 block still present")
}
if b == fun.blocks["b3"] {
t.Errorf("b3 block still present")
}
for _, v := range b.Values {
if v == fun.values["cond"] {
t.Errorf("constant condition still present")
}
}
}
}
......@@ -105,6 +105,7 @@ var genericOps = []opData{
var genericBlocks = []blockData{
{name: "Exit"}, // no successors. There should only be 1 of these.
{name: "Dead"}, // no successors; determined to be dead but not yet removed
{name: "Plain"}, // a single successor
{name: "If"}, // 2 successors, if control goto Succs[0] else goto Succs[1]
{name: "Call"}, // 2 successors, normal return and panic
......
......@@ -19,6 +19,7 @@ const (
BlockAMD64UGE
BlockExit
BlockDead
BlockPlain
BlockIf
BlockCall
......@@ -39,6 +40,7 @@ var blockString = [...]string{
BlockAMD64UGE: "UGE",
BlockExit: "Exit",
BlockDead: "Dead",
BlockPlain: "Plain",
BlockIf: "If",
BlockCall: "Call",
......
......@@ -23,6 +23,9 @@ func applyRewrite(f *Func, rb func(*Block) bool, rv func(*Value, *Config) bool)
for {
change := false
for _, b := range f.Blocks {
if b.Kind == BlockDead {
continue
}
if b.Control != nil && b.Control.Op == OpCopy {
for b.Control.Op == OpCopy {
b.Control = b.Control.Args[0]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment