Skip to content

Commit 5736eb0

Browse files
committed
cmd/compile: support inlining of type switches
This CL adds support for inlining type switches, including exporting and importing them. Type switches are represented mostly the same as expression switches. However, if the type switch guard includes a short variable declaration, then there are two differences: (1) there's an ONONAME (in the OTYPESW's Left) to represent the overall pseudo declaration; and (2) there's an ONAME (in each OCASE's Rlist) to represent the per-case variables. For simplicity, this CL simply writes out each variable separately using iimport/iiexport's normal Vargen mechanism for disambiguating identically named variables within a function. This could be improved somewhat, but inlinable type switches are probably too uncommon to merit the complexity. While here, remove "case OCASE" from typecheck1. We only type check "case" clauses as part of a "select" or "switch" statement, never as standalone statements. Fixes #37837 Change-Id: I8f42f6c9afdd821d6202af4a6bf1dbcbba0ef424 Reviewed-on: https://go-review.googlesource.com/c/go/+/266203 Run-TryBot: Matthew Dempsky <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Keith Randall <[email protected]> Trust: Matthew Dempsky <[email protected]>
1 parent 362d25f commit 5736eb0

File tree

8 files changed

+140
-25
lines changed

8 files changed

+140
-25
lines changed

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

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,13 +1138,10 @@ func (w *exportWriter) stmt(n *Node) {
11381138
w.pos(n.Pos)
11391139
w.stmtList(n.Ninit)
11401140
w.exprsOrNil(n.Left, nil)
1141-
w.stmtList(n.List)
1141+
w.caseList(n)
11421142

1143-
case OCASE:
1144-
w.op(OCASE)
1145-
w.pos(n.Pos)
1146-
w.stmtList(n.List)
1147-
w.stmtList(n.Nbody)
1143+
// case OCASE:
1144+
// handled by caseList
11481145

11491146
case OFALL:
11501147
w.op(OFALL)
@@ -1168,6 +1165,24 @@ func (w *exportWriter) stmt(n *Node) {
11681165
}
11691166
}
11701167

1168+
func (w *exportWriter) caseList(sw *Node) {
1169+
namedTypeSwitch := sw.Op == OSWITCH && sw.Left != nil && sw.Left.Op == OTYPESW && sw.Left.Left != nil
1170+
1171+
cases := sw.List.Slice()
1172+
w.uint64(uint64(len(cases)))
1173+
for _, cas := range cases {
1174+
if cas.Op != OCASE {
1175+
Fatalf("expected OCASE, got %v", cas)
1176+
}
1177+
w.pos(cas.Pos)
1178+
w.stmtList(cas.List)
1179+
if namedTypeSwitch {
1180+
w.localName(cas.Rlist.First())
1181+
}
1182+
w.stmtList(cas.Nbody)
1183+
}
1184+
}
1185+
11711186
func (w *exportWriter) exprList(list Nodes) {
11721187
for _, n := range list.Slice() {
11731188
w.expr(n)
@@ -1232,6 +1247,19 @@ func (w *exportWriter) expr(n *Node) {
12321247
w.op(OTYPE)
12331248
w.typ(n.Type)
12341249

1250+
case OTYPESW:
1251+
w.op(OTYPESW)
1252+
w.pos(n.Pos)
1253+
var s *types.Sym
1254+
if n.Left != nil {
1255+
if n.Left.Op != ONONAME {
1256+
Fatalf("expected ONONAME, got %v", n.Left)
1257+
}
1258+
s = n.Left.Sym
1259+
}
1260+
w.localIdent(s, 0) // declared pseudo-variable, if any
1261+
w.exprsOrNil(n.Right, nil)
1262+
12351263
// case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC:
12361264
// should have been resolved by typechecking - handled by default case
12371265

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

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,28 @@ func (r *importReader) stmtList() []*Node {
784784
return list
785785
}
786786

787+
func (r *importReader) caseList(sw *Node) []*Node {
788+
namedTypeSwitch := sw.Op == OSWITCH && sw.Left != nil && sw.Left.Op == OTYPESW && sw.Left.Left != nil
789+
790+
cases := make([]*Node, r.uint64())
791+
for i := range cases {
792+
cas := nodl(r.pos(), OCASE, nil, nil)
793+
cas.List.Set(r.stmtList())
794+
if namedTypeSwitch {
795+
// Note: per-case variables will have distinct, dotted
796+
// names after import. That's okay: swt.go only needs
797+
// Sym for diagnostics anyway.
798+
caseVar := newnamel(cas.Pos, r.ident())
799+
declare(caseVar, dclcontext)
800+
cas.Rlist.Set1(caseVar)
801+
caseVar.Name.Defn = sw.Left
802+
}
803+
cas.Nbody.Set(r.stmtList())
804+
cases[i] = cas
805+
}
806+
return cases
807+
}
808+
787809
func (r *importReader) exprList() []*Node {
788810
var list []*Node
789811
for {
@@ -831,6 +853,14 @@ func (r *importReader) node() *Node {
831853
case OTYPE:
832854
return typenod(r.typ())
833855

856+
case OTYPESW:
857+
n := nodl(r.pos(), OTYPESW, nil, nil)
858+
if s := r.ident(); s != nil {
859+
n.Left = npos(n.Pos, newnoname(s))
860+
}
861+
n.Right, _ = r.exprsOrNil()
862+
return n
863+
834864
// case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC:
835865
// unreachable - should have been resolved by typechecking
836866

@@ -1025,16 +1055,11 @@ func (r *importReader) node() *Node {
10251055
n := nodl(r.pos(), op, nil, nil)
10261056
n.Ninit.Set(r.stmtList())
10271057
n.Left, _ = r.exprsOrNil()
1028-
n.List.Set(r.stmtList())
1058+
n.List.Set(r.caseList(n))
10291059
return n
10301060

1031-
case OCASE:
1032-
n := nodl(r.pos(), OCASE, nil, nil)
1033-
n.List.Set(r.exprList())
1034-
// TODO(gri) eventually we must declare variables for type switch
1035-
// statements (type switch statements are not yet exported)
1036-
n.Nbody.Set(r.stmtList())
1037-
return n
1061+
// case OCASE:
1062+
// handled by caseList
10381063

10391064
case OFALL:
10401065
n := nodl(r.pos(), OFALL, nil, nil)

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -392,13 +392,9 @@ func (v *hairyVisitor) visit(n *Node) bool {
392392
v.reason = "call to recover"
393393
return true
394394

395-
case OCALLPART:
396-
// OCALLPART is inlineable, but no extra cost to the budget
397-
398395
case OCLOSURE,
399396
ORANGE,
400397
OSELECT,
401-
OTYPESW,
402398
OGO,
403399
ODEFER,
404400
ODCLTYPE, // can't print yet

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,11 +2065,6 @@ func typecheck1(n *Node, top int) (res *Node) {
20652065
n.Type = nil
20662066
return n
20672067

2068-
case OCASE:
2069-
ok |= ctxStmt
2070-
typecheckslice(n.List.Slice(), ctxExpr)
2071-
typecheckslice(n.Nbody.Slice(), ctxStmt)
2072-
20732068
case ODCLFUNC:
20742069
ok |= ctxStmt
20752070
typecheckfunc(n)

test/fixedbugs/issue37837.dir/a.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2020 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(i interface{}) int { // ERROR "can inline F" "i does not escape"
8+
switch i.(type) {
9+
case nil:
10+
return 0
11+
case int:
12+
return 1
13+
case float64:
14+
return 2
15+
default:
16+
return 3
17+
}
18+
}
19+
20+
func G(i interface{}) interface{} { // ERROR "can inline G" "leaking param: i"
21+
switch i := i.(type) {
22+
case nil: // ERROR "moved to heap: i"
23+
return &i
24+
case int: // ERROR "moved to heap: i"
25+
return &i
26+
case float64: // ERROR "moved to heap: i"
27+
return &i
28+
case string, []byte: // ERROR "moved to heap: i"
29+
return &i
30+
default: // ERROR "moved to heap: i"
31+
return &i
32+
}
33+
}

test/fixedbugs/issue37837.dir/b.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2020 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+
// Test that inlined type switches without short variable
11+
// declarations work correctly.
12+
check(0, a.F(nil)) // ERROR "inlining call to a.F"
13+
check(1, a.F(0)) // ERROR "inlining call to a.F" "does not escape"
14+
check(2, a.F(0.0)) // ERROR "inlining call to a.F" "does not escape"
15+
check(3, a.F("")) // ERROR "inlining call to a.F" "does not escape"
16+
17+
// Test that inlined type switches with short variable
18+
// declarations work correctly.
19+
_ = a.G(nil).(*interface{}) // ERROR "inlining call to a.G"
20+
_ = a.G(1).(*int) // ERROR "inlining call to a.G" "does not escape"
21+
_ = a.G(2.0).(*float64) // ERROR "inlining call to a.G" "does not escape"
22+
_ = (*a.G("").(*interface{})).(string) // ERROR "inlining call to a.G" "does not escape"
23+
_ = (*a.G(([]byte)(nil)).(*interface{})).([]byte) // ERROR "inlining call to a.G" "does not escape"
24+
_ = (*a.G(true).(*interface{})).(bool) // ERROR "inlining call to a.G" "does not escape"
25+
}
26+
27+
//go:noinline
28+
func check(want, got int) {
29+
if want != got {
30+
println("want", want, "but got", got)
31+
}
32+
}

test/fixedbugs/issue37837.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// errorcheckandrundir -0 -m
2+
3+
// Copyright 2020 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/inline.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,7 @@ func switchBreak(x, y int) int {
152152
return n
153153
}
154154

155-
// can't currently inline functions with a type switch
156-
func switchType(x interface{}) int { // ERROR "x does not escape"
155+
func switchType(x interface{}) int { // ERROR "can inline switchType" "x does not escape"
157156
switch x.(type) {
158157
case int:
159158
return x.(int)

0 commit comments

Comments
 (0)