1
0
mirror of https://github.com/golang/go synced 2024-10-05 18:21:21 -06:00

[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/12004
Reviewed-by: Todd Neal <todd@tneal.org>
Reviewed-by: Michael Matloob <michaelmatloob@gmail.com>
This commit is contained in:
Josh Bleecher Snyder 2015-07-09 21:24:12 -06:00
parent 9201c86b57
commit 9b048527db
7 changed files with 64 additions and 3 deletions

View File

@ -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))

View File

@ -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},

View File

@ -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
}
}
}

View File

@ -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")
}
}
}
}

View File

@ -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

View File

@ -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",

View File

@ -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]