]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: support inlining of type switches
authorMatthew Dempsky <mdempsky@google.com>
Thu, 29 Oct 2020 08:13:16 +0000 (01:13 -0700)
committerMatthew Dempsky <mdempsky@google.com>
Fri, 6 Nov 2020 20:49:11 +0000 (20:49 +0000)
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 <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Trust: Matthew Dempsky <mdempsky@google.com>

src/cmd/compile/internal/gc/iexport.go
src/cmd/compile/internal/gc/iimport.go
src/cmd/compile/internal/gc/inl.go
src/cmd/compile/internal/gc/typecheck.go
test/fixedbugs/issue37837.dir/a.go [new file with mode: 0644]
test/fixedbugs/issue37837.dir/b.go [new file with mode: 0644]
test/fixedbugs/issue37837.go [new file with mode: 0644]
test/inline.go

index 9bc1f64600d6e379c6b59eecad94cc4db7ff4351..1f53d8ca7dc195bc26d5d68a5e7bf10d729b5f1d 100644 (file)
@@ -1138,13 +1138,10 @@ func (w *exportWriter) stmt(n *Node) {
                w.pos(n.Pos)
                w.stmtList(n.Ninit)
                w.exprsOrNil(n.Left, nil)
-               w.stmtList(n.List)
+               w.caseList(n)
 
-       case OCASE:
-               w.op(OCASE)
-               w.pos(n.Pos)
-               w.stmtList(n.List)
-               w.stmtList(n.Nbody)
+       // case OCASE:
+       //      handled by caseList
 
        case OFALL:
                w.op(OFALL)
@@ -1168,6 +1165,24 @@ func (w *exportWriter) stmt(n *Node) {
        }
 }
 
+func (w *exportWriter) caseList(sw *Node) {
+       namedTypeSwitch := sw.Op == OSWITCH && sw.Left != nil && sw.Left.Op == OTYPESW && sw.Left.Left != nil
+
+       cases := sw.List.Slice()
+       w.uint64(uint64(len(cases)))
+       for _, cas := range cases {
+               if cas.Op != OCASE {
+                       Fatalf("expected OCASE, got %v", cas)
+               }
+               w.pos(cas.Pos)
+               w.stmtList(cas.List)
+               if namedTypeSwitch {
+                       w.localName(cas.Rlist.First())
+               }
+               w.stmtList(cas.Nbody)
+       }
+}
+
 func (w *exportWriter) exprList(list Nodes) {
        for _, n := range list.Slice() {
                w.expr(n)
@@ -1232,6 +1247,19 @@ func (w *exportWriter) expr(n *Node) {
                w.op(OTYPE)
                w.typ(n.Type)
 
+       case OTYPESW:
+               w.op(OTYPESW)
+               w.pos(n.Pos)
+               var s *types.Sym
+               if n.Left != nil {
+                       if n.Left.Op != ONONAME {
+                               Fatalf("expected ONONAME, got %v", n.Left)
+                       }
+                       s = n.Left.Sym
+               }
+               w.localIdent(s, 0) // declared pseudo-variable, if any
+               w.exprsOrNil(n.Right, nil)
+
        // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC:
        //      should have been resolved by typechecking - handled by default case
 
index 7f2b05f288f7e18109bcd7f6ffa73ab5e735cbd0..c0114d0e53b166be1daf6cc9554006ad744b1ff2 100644 (file)
@@ -784,6 +784,28 @@ func (r *importReader) stmtList() []*Node {
        return list
 }
 
+func (r *importReader) caseList(sw *Node) []*Node {
+       namedTypeSwitch := sw.Op == OSWITCH && sw.Left != nil && sw.Left.Op == OTYPESW && sw.Left.Left != nil
+
+       cases := make([]*Node, r.uint64())
+       for i := range cases {
+               cas := nodl(r.pos(), OCASE, nil, nil)
+               cas.List.Set(r.stmtList())
+               if namedTypeSwitch {
+                       // Note: per-case variables will have distinct, dotted
+                       // names after import. That's okay: swt.go only needs
+                       // Sym for diagnostics anyway.
+                       caseVar := newnamel(cas.Pos, r.ident())
+                       declare(caseVar, dclcontext)
+                       cas.Rlist.Set1(caseVar)
+                       caseVar.Name.Defn = sw.Left
+               }
+               cas.Nbody.Set(r.stmtList())
+               cases[i] = cas
+       }
+       return cases
+}
+
 func (r *importReader) exprList() []*Node {
        var list []*Node
        for {
@@ -831,6 +853,14 @@ func (r *importReader) node() *Node {
        case OTYPE:
                return typenod(r.typ())
 
+       case OTYPESW:
+               n := nodl(r.pos(), OTYPESW, nil, nil)
+               if s := r.ident(); s != nil {
+                       n.Left = npos(n.Pos, newnoname(s))
+               }
+               n.Right, _ = r.exprsOrNil()
+               return n
+
        // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC:
        //      unreachable - should have been resolved by typechecking
 
@@ -1025,16 +1055,11 @@ func (r *importReader) node() *Node {
                n := nodl(r.pos(), op, nil, nil)
                n.Ninit.Set(r.stmtList())
                n.Left, _ = r.exprsOrNil()
-               n.List.Set(r.stmtList())
+               n.List.Set(r.caseList(n))
                return n
 
-       case OCASE:
-               n := nodl(r.pos(), OCASE, nil, nil)
-               n.List.Set(r.exprList())
-               // TODO(gri) eventually we must declare variables for type switch
-               // statements (type switch statements are not yet exported)
-               n.Nbody.Set(r.stmtList())
-               return n
+       // case OCASE:
+       //      handled by caseList
 
        case OFALL:
                n := nodl(r.pos(), OFALL, nil, nil)
index 253036fea6417b8167f88a02877f59f1b2ca13e2..139572f652e0b98c88117f42e8f9da205c05a3af 100644 (file)
@@ -392,13 +392,9 @@ func (v *hairyVisitor) visit(n *Node) bool {
                v.reason = "call to recover"
                return true
 
-       case OCALLPART:
-               // OCALLPART is inlineable, but no extra cost to the budget
-
        case OCLOSURE,
                ORANGE,
                OSELECT,
-               OTYPESW,
                OGO,
                ODEFER,
                ODCLTYPE, // can't print yet
index 8ebeaf1330c0eb9a64693fb38cb894c8852b1bec..cbba5ff79ccaff9162aac8fbf364db102d0f78c1 100644 (file)
@@ -2065,11 +2065,6 @@ func typecheck1(n *Node, top int) (res *Node) {
                n.Type = nil
                return n
 
-       case OCASE:
-               ok |= ctxStmt
-               typecheckslice(n.List.Slice(), ctxExpr)
-               typecheckslice(n.Nbody.Slice(), ctxStmt)
-
        case ODCLFUNC:
                ok |= ctxStmt
                typecheckfunc(n)
diff --git a/test/fixedbugs/issue37837.dir/a.go b/test/fixedbugs/issue37837.dir/a.go
new file mode 100644 (file)
index 0000000..49d830f
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package a
+
+func F(i interface{}) int { // ERROR "can inline F" "i does not escape"
+       switch i.(type) {
+       case nil:
+               return 0
+       case int:
+               return 1
+       case float64:
+               return 2
+       default:
+               return 3
+       }
+}
+
+func G(i interface{}) interface{} { // ERROR "can inline G" "leaking param: i"
+       switch i := i.(type) {
+       case nil: // ERROR "moved to heap: i"
+               return &i
+       case int: // ERROR "moved to heap: i"
+               return &i
+       case float64: // ERROR "moved to heap: i"
+               return &i
+       case string, []byte: // ERROR "moved to heap: i"
+               return &i
+       default: // ERROR "moved to heap: i"
+               return &i
+       }
+}
diff --git a/test/fixedbugs/issue37837.dir/b.go b/test/fixedbugs/issue37837.dir/b.go
new file mode 100644 (file)
index 0000000..461f5c7
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "./a"
+
+func main() {
+       // Test that inlined type switches without short variable
+       // declarations work correctly.
+       check(0, a.F(nil)) // ERROR "inlining call to a.F"
+       check(1, a.F(0))   // ERROR "inlining call to a.F" "does not escape"
+       check(2, a.F(0.0)) // ERROR "inlining call to a.F" "does not escape"
+       check(3, a.F(""))  // ERROR "inlining call to a.F" "does not escape"
+
+       // Test that inlined type switches with short variable
+       // declarations work correctly.
+       _ = a.G(nil).(*interface{})                       // ERROR "inlining call to a.G"
+       _ = a.G(1).(*int)                                 // ERROR "inlining call to a.G" "does not escape"
+       _ = a.G(2.0).(*float64)                           // ERROR "inlining call to a.G" "does not escape"
+       _ = (*a.G("").(*interface{})).(string)            // ERROR "inlining call to a.G" "does not escape"
+       _ = (*a.G(([]byte)(nil)).(*interface{})).([]byte) // ERROR "inlining call to a.G" "does not escape"
+       _ = (*a.G(true).(*interface{})).(bool)            // ERROR "inlining call to a.G" "does not escape"
+}
+
+//go:noinline
+func check(want, got int) {
+       if want != got {
+               println("want", want, "but got", got)
+       }
+}
diff --git a/test/fixedbugs/issue37837.go b/test/fixedbugs/issue37837.go
new file mode 100644 (file)
index 0000000..2e8abc5
--- /dev/null
@@ -0,0 +1,7 @@
+// errorcheckandrundir -0 -m
+
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ignored
index 470414f883a451aa66d8f8414637194bb7d6a276..d754f06e0340c47025c53e80ad21c9b0ade18d56 100644 (file)
@@ -152,8 +152,7 @@ func switchBreak(x, y int) int {
        return n
 }
 
-// can't currently inline functions with a type switch
-func switchType(x interface{}) int { // ERROR "x does not escape"
+func switchType(x interface{}) int { // ERROR "can inline switchType" "x does not escape"
        switch x.(type) {
        case int:
                return x.(int)