Skip to content

Commit dffe5ac

Browse files
committed
cmd/compile: eliminate dead code in if statements after typechecking
This is a more thorough and cleaner fix than doing dead code elimination separately during inlining, escape analysis, and export. Unfortunately, it does add another full walk of the AST. The performance impact is very small, but not non-zero. If a label or goto is present in the dead code, it is not eliminated. This restriction can be removed once label/goto checking occurs much earlier in the compiler. In practice, it probably doesn't matter much. Updates #19699 Fixes #19705 name old alloc/op new alloc/op delta Template 39.2MB ± 0% 39.3MB ± 0% +0.28% (p=0.008 n=5+5) Unicode 29.8MB ± 0% 29.8MB ± 0% ~ (p=1.000 n=5+5) GoTypes 113MB ± 0% 113MB ± 0% -0.55% (p=0.008 n=5+5) SSA 1.25GB ± 0% 1.25GB ± 0% +0.02% (p=0.008 n=5+5) Flate 25.3MB ± 0% 25.3MB ± 0% -0.24% (p=0.032 n=5+5) GoParser 31.7MB ± 0% 31.8MB ± 0% +0.31% (p=0.008 n=5+5) Reflect 78.2MB ± 0% 78.3MB ± 0% ~ (p=0.421 n=5+5) Tar 26.6MB ± 0% 26.7MB ± 0% +0.21% (p=0.008 n=5+5) XML 42.2MB ± 0% 42.2MB ± 0% ~ (p=0.056 n=5+5) name old allocs/op new allocs/op delta Template 385k ± 0% 387k ± 0% +0.51% (p=0.016 n=5+5) Unicode 321k ± 0% 321k ± 0% ~ (p=1.000 n=5+5) GoTypes 1.14M ± 0% 1.14M ± 0% ~ (p=1.000 n=5+5) SSA 9.71M ± 0% 9.72M ± 0% +0.10% (p=0.008 n=5+5) Flate 234k ± 1% 234k ± 1% ~ (p=0.690 n=5+5) GoParser 315k ± 0% 317k ± 0% +0.71% (p=0.008 n=5+5) Reflect 980k ± 0% 983k ± 0% +0.30% (p=0.032 n=5+5) Tar 251k ± 0% 252k ± 0% +0.55% (p=0.016 n=5+5) XML 392k ± 0% 393k ± 0% +0.30% (p=0.008 n=5+5) Change-Id: Ia10ff4bbf5c6eae782582cc9cbc9785494d4fb83 Reviewed-on: https://go-review.googlesource.com/38773 Run-TryBot: Josh Bleecher Snyder <[email protected]> Reviewed-by: Matthew Dempsky <[email protected]>
1 parent 2ef340f commit dffe5ac

File tree

10 files changed

+121
-55
lines changed

10 files changed

+121
-55
lines changed

src/cmd/compile/internal/gc/bexport.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,22 +1469,8 @@ func (p *exporter) stmt(n *Node) {
14691469
p.pos(n)
14701470
p.stmtList(n.Ninit)
14711471
p.expr(n.Left)
1472-
nbody := n.Nbody
1473-
rlist := n.Rlist
1474-
if Isconst(n.Left, CTBOOL) {
1475-
// if false { ... } or if true { ... }
1476-
// Only export the taken branch.
1477-
// This is more efficient,
1478-
// and avoids trying to export
1479-
// un-exportable nodes.
1480-
if n.Left.Bool() {
1481-
rlist = Nodes{}
1482-
} else {
1483-
nbody = Nodes{}
1484-
}
1485-
}
1486-
p.stmtList(nbody)
1487-
p.stmtList(rlist)
1472+
p.stmtList(n.Nbody)
1473+
p.stmtList(n.Rlist)
14881474

14891475
case OFOR:
14901476
p.op(OFOR)

src/cmd/compile/internal/gc/esc.go

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -690,20 +690,11 @@ func (e *EscState) esc(n *Node, parent *Node) {
690690
e.escassignSinkWhy(n, n, "too large for stack") // TODO category: tooLarge
691691
}
692692

693-
if n.Op == OIF && Isconst(n.Left, CTBOOL) {
694-
// Don't examine dead code.
695-
if n.Left.Bool() {
696-
e.esclist(n.Nbody, n)
697-
} else {
698-
e.esclist(n.Rlist, n)
699-
}
700-
} else {
701-
e.esc(n.Left, n)
702-
e.esc(n.Right, n)
703-
e.esclist(n.Nbody, n)
704-
e.esclist(n.List, n)
705-
e.esclist(n.Rlist, n)
706-
}
693+
e.esc(n.Left, n)
694+
e.esc(n.Right, n)
695+
e.esclist(n.Nbody, n)
696+
e.esclist(n.List, n)
697+
e.esclist(n.Rlist, n)
707698

708699
if n.Op == OFOR || n.Op == OFORUNTIL || n.Op == ORANGE {
709700
e.loopdepth--

src/cmd/compile/internal/gc/inl.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -279,16 +279,6 @@ func ishairy(n *Node, budget *int32, reason *string) bool {
279279
return true
280280
}
281281

282-
if n.Op == OIF && Isconst(n.Left, CTBOOL) {
283-
var taken Nodes // statements for the branch that is always taken
284-
if n.Left.Bool() {
285-
taken = n.Nbody // then case
286-
} else {
287-
taken = n.Rlist // else case
288-
}
289-
return ishairylist(n.Ninit, budget, reason) || ishairylist(taken, budget, reason)
290-
}
291-
292282
return ishairy(n.Left, budget, reason) || ishairy(n.Right, budget, reason) ||
293283
ishairylist(n.List, budget, reason) || ishairylist(n.Rlist, budget, reason) ||
294284
ishairylist(n.Ninit, budget, reason) || ishairylist(n.Nbody, budget, reason)

src/cmd/compile/internal/gc/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,9 @@ func Main(archInit func(*Arch)) {
443443
if nerrors != 0 {
444444
Curfn.Nbody.Set(nil) // type errors; do not compile
445445
}
446+
// Now that we've checked whether n terminates,
447+
// we can eliminate some obviously dead code.
448+
deadcode(Curfn)
446449
fcount++
447450
}
448451
}

src/cmd/compile/internal/gc/typecheck.go

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3842,13 +3842,7 @@ func markbreak(n *Node, implicit *Node) {
38423842
lab.SetHasBreak(true)
38433843
}
38443844
}
3845-
3846-
case OFOR,
3847-
OFORUNTIL,
3848-
OSWITCH,
3849-
OTYPESW,
3850-
OSELECT,
3851-
ORANGE:
3845+
case OFOR, OFORUNTIL, OSWITCH, OTYPESW, OSELECT, ORANGE:
38523846
implicit = n
38533847
fallthrough
38543848
default:
@@ -3883,8 +3877,7 @@ func markbreaklist(l Nodes, implicit *Node) {
38833877
}
38843878
}
38853879

3886-
// Isterminating whether the Nodes list ends with a terminating
3887-
// statement.
3880+
// isterminating reports whether the Nodes list ends with a terminating statement.
38883881
func (l Nodes) isterminating() bool {
38893882
s := l.Slice()
38903883
c := len(s)
@@ -3894,7 +3887,7 @@ func (l Nodes) isterminating() bool {
38943887
return s[c-1].isterminating()
38953888
}
38963889

3897-
// Isterminating returns whether the node n, the last one in a
3890+
// Isterminating reports whether the node n, the last one in a
38983891
// statement list, is a terminating statement.
38993892
func (n *Node) isterminating() bool {
39003893
switch n.Op {
@@ -3906,11 +3899,7 @@ func (n *Node) isterminating() bool {
39063899
case OBLOCK:
39073900
return n.List.isterminating()
39083901

3909-
case OGOTO,
3910-
ORETURN,
3911-
ORETJMP,
3912-
OPANIC,
3913-
OXFALL:
3902+
case OGOTO, ORETURN, ORETJMP, OPANIC, OXFALL:
39143903
return true
39153904

39163905
case OFOR, OFORUNTIL:
@@ -3948,6 +3937,7 @@ func (n *Node) isterminating() bool {
39483937
return false
39493938
}
39503939

3940+
// checkreturn makes sure that fn terminates appropriately.
39513941
func checkreturn(fn *Node) {
39523942
if fn.Type.Results().NumFields() != 0 && fn.Nbody.Len() != 0 {
39533943
markbreaklist(fn.Nbody, nil)
@@ -3956,3 +3946,48 @@ func checkreturn(fn *Node) {
39563946
}
39573947
}
39583948
}
3949+
3950+
func deadcode(fn *Node) {
3951+
deadcodeslice(fn.Nbody)
3952+
}
3953+
3954+
func deadcodeslice(nn Nodes) {
3955+
for _, n := range nn.Slice() {
3956+
if n == nil {
3957+
continue
3958+
}
3959+
if n.Op == OIF && Isconst(n.Left, CTBOOL) {
3960+
var dead *Nodes
3961+
if n.Left.Bool() {
3962+
dead = &n.Rlist
3963+
} else {
3964+
dead = &n.Nbody
3965+
}
3966+
// TODO(mdempsky/josharian): eliminate need for haslabelgoto
3967+
// by checking labels and gotos earlier. See issue 19699.
3968+
if !(*dead).haslabelgoto() {
3969+
*dead = Nodes{}
3970+
}
3971+
}
3972+
deadcodeslice(n.Ninit)
3973+
deadcodeslice(n.Nbody)
3974+
deadcodeslice(n.List)
3975+
deadcodeslice(n.Rlist)
3976+
}
3977+
}
3978+
3979+
// haslabelgoto reports whether the Nodes list contains any label or goto statements.
3980+
func (l Nodes) haslabelgoto() bool {
3981+
for _, n := range l.Slice() {
3982+
if n == nil {
3983+
continue
3984+
}
3985+
if n.Op == OLABEL || n.Op == OGOTO {
3986+
return true
3987+
}
3988+
if n.Ninit.haslabelgoto() || n.Nbody.haslabelgoto() || n.List.haslabelgoto() || n.Rlist.haslabelgoto() {
3989+
return true
3990+
}
3991+
}
3992+
return false
3993+
}

test/fixedbugs/issue19699.dir/a.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package a
6+
7+
func F() {
8+
l1:
9+
if false {
10+
goto l1
11+
}
12+
}

test/fixedbugs/issue19699.dir/b.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import "./a"
8+
9+
func main() {
10+
a.F()
11+
}

test/fixedbugs/issue19699.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// compiledir
2+
3+
// Copyright 2017 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package ignored

test/fixedbugs/issue19699b.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// errorcheck
2+
3+
// Copyright 2017 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package p
8+
9+
func f() bool {
10+
if false {
11+
} else {
12+
return true
13+
}
14+
} // ERROR "missing return at end of function"

test/fixedbugs/issue19705.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// compile
2+
3+
// Copyright 2017 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package p
8+
9+
func f1() {
10+
f2()
11+
}
12+
13+
func f2() {
14+
if false {
15+
_ = func() {}
16+
}
17+
}

0 commit comments

Comments
 (0)