Skip to content

Commit cbf91d8

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 golang#19699 Fixes golang#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
1 parent d206af1 commit cbf91d8

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
@@ -446,6 +446,9 @@ func Main(archInit func(*Arch)) {
446446
if nerrors != 0 {
447447
Curfn.Nbody.Set(nil) // type errors; do not compile
448448
}
449+
// Now that we've checked whether n terminates,
450+
// we can eliminate some obviously dead code.
451+
deadcode(Curfn)
449452
fcount++
450453
}
451454
}

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

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3897,13 +3897,7 @@ func markbreak(n *Node, implicit *Node) {
38973897
lab.SetHasBreak(true)
38983898
}
38993899
}
3900-
3901-
case OFOR,
3902-
OFORUNTIL,
3903-
OSWITCH,
3904-
OTYPESW,
3905-
OSELECT,
3906-
ORANGE:
3900+
case OFOR, OFORUNTIL, OSWITCH, OTYPESW, OSELECT, ORANGE:
39073901
implicit = n
39083902
fallthrough
39093903
default:
@@ -3938,8 +3932,7 @@ func markbreaklist(l Nodes, implicit *Node) {
39383932
}
39393933
}
39403934

3941-
// Isterminating whether the Nodes list ends with a terminating
3942-
// statement.
3935+
// isterminating reports whether the Nodes list ends with a terminating statement.
39433936
func (l Nodes) isterminating() bool {
39443937
s := l.Slice()
39453938
c := len(s)
@@ -3949,7 +3942,7 @@ func (l Nodes) isterminating() bool {
39493942
return s[c-1].isterminating()
39503943
}
39513944

3952-
// Isterminating returns whether the node n, the last one in a
3945+
// Isterminating reports whether the node n, the last one in a
39533946
// statement list, is a terminating statement.
39543947
func (n *Node) isterminating() bool {
39553948
switch n.Op {
@@ -3961,11 +3954,7 @@ func (n *Node) isterminating() bool {
39613954
case OBLOCK:
39623955
return n.List.isterminating()
39633956

3964-
case OGOTO,
3965-
ORETURN,
3966-
ORETJMP,
3967-
OPANIC,
3968-
OXFALL:
3957+
case OGOTO, ORETURN, ORETJMP, OPANIC, OXFALL:
39693958
return true
39703959

39713960
case OFOR, OFORUNTIL:
@@ -4003,6 +3992,7 @@ func (n *Node) isterminating() bool {
40033992
return false
40043993
}
40053994

3995+
// checkreturn makes sure that fn terminates appropriately.
40063996
func checkreturn(fn *Node) {
40073997
if fn.Type.Results().NumFields() != 0 && fn.Nbody.Len() != 0 {
40083998
markbreaklist(fn.Nbody, nil)
@@ -4011,3 +4001,48 @@ func checkreturn(fn *Node) {
40114001
}
40124002
}
40134003
}
4004+
4005+
func deadcode(fn *Node) {
4006+
deadcodeslice(fn.Nbody)
4007+
}
4008+
4009+
func deadcodeslice(nn Nodes) {
4010+
for _, n := range nn.Slice() {
4011+
if n == nil {
4012+
continue
4013+
}
4014+
if n.Op == OIF && Isconst(n.Left, CTBOOL) {
4015+
var dead *Nodes
4016+
if n.Left.Bool() {
4017+
dead = &n.Rlist
4018+
} else {
4019+
dead = &n.Nbody
4020+
}
4021+
// TODO(mdempsky/josharian): eliminate need for haslabelgoto
4022+
// by checking labels and gotos earlier. See issue 19699.
4023+
if !(*dead).haslabelgoto() {
4024+
*dead = Nodes{}
4025+
}
4026+
}
4027+
deadcodeslice(n.Ninit)
4028+
deadcodeslice(n.Nbody)
4029+
deadcodeslice(n.List)
4030+
deadcodeslice(n.Rlist)
4031+
}
4032+
}
4033+
4034+
// haslabelgoto reports whether the Nodes list contains any label or goto statements.
4035+
func (l Nodes) haslabelgoto() bool {
4036+
for _, n := range l.Slice() {
4037+
if n == nil {
4038+
continue
4039+
}
4040+
if n.Op == OLABEL || n.Op == OGOTO {
4041+
return true
4042+
}
4043+
if n.Ninit.haslabelgoto() || n.Nbody.haslabelgoto() || n.List.haslabelgoto() || n.Rlist.haslabelgoto() {
4044+
return true
4045+
}
4046+
}
4047+
return false
4048+
}

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)