Commit 85c6f71b authored by Josh Bleecher Snyder's avatar Josh Bleecher Snyder

cmd/internal/gc: clean up switch code

This CL makes the switch walking and typechecking code
more idiomatic and adds documentation.
It also removes all but one global variable.

No functional changes. Confirmed with toolstash -cmp on the stdlib.

Change-Id: Ic3f38acc66e906edd722498839aeb557863639cf
Reviewed-on: https://go-review.googlesource.com/6268Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent 59cc5a19
...@@ -7,257 +7,323 @@ package gc ...@@ -7,257 +7,323 @@ package gc
import ( import (
"cmd/internal/obj" "cmd/internal/obj"
"fmt" "fmt"
"sort"
"strconv"
) )
const ( const (
Snorm = 0 + iota // expression switch
Strue switchKindExpr = iota // switch a {...} or switch 5 {...}
Sfalse switchKindTrue // switch true {...} or switch {...}
Stype switchKindFalse // switch false {...}
Tdefault
Texprconst
Texprvar
Ttypenil
Ttypeconst
Ttypevar
Ncase = 4
)
type Case struct {
node *Node
hash uint32
type_ uint8
diag uint8
ordinal uint16
link *Case
}
var C *Case
func dumpcase(c0 *Case) {
for c := c0; c != nil; c = c.link {
switch c.type_ {
case Tdefault:
fmt.Printf("case-default\n")
fmt.Printf("\tord=%d\n", c.ordinal)
case Texprconst:
fmt.Printf("case-exprconst\n")
fmt.Printf("\tord=%d\n", c.ordinal)
case Texprvar: // type switch
fmt.Printf("case-exprvar\n") switchKindType // switch a.(type) {...}
fmt.Printf("\tord=%d\n", c.ordinal) )
fmt.Printf("\top=%v\n", Oconv(int(c.node.Left.Op), 0))
case Ttypenil: const (
fmt.Printf("case-typenil\n") caseKindDefault = iota // default:
fmt.Printf("\tord=%d\n", c.ordinal)
case Ttypeconst: // expression switch
fmt.Printf("case-typeconst\n") caseKindExprConst // case 5:
fmt.Printf("\tord=%d\n", c.ordinal) caseKindExprVar // case x:
fmt.Printf("\thash=%x\n", c.hash)
case Ttypevar: // type switch
fmt.Printf("case-typevar\n") caseKindTypeNil // case nil:
fmt.Printf("\tord=%d\n", c.ordinal) caseKindTypeConst // case time.Time: (concrete type, has type hash)
caseKindTypeVar // case io.Reader: (interface type)
)
default: const binarySearchMin = 4 // minimum number of cases for binary search
fmt.Printf("case-???\n")
fmt.Printf("\tord=%d\n", c.ordinal)
fmt.Printf("\top=%v\n", Oconv(int(c.node.Left.Op), 0))
fmt.Printf("\thash=%x\n", c.hash)
}
}
fmt.Printf("\n") // An exprSwitch walks an expression switch.
type exprSwitch struct {
exprname *Node // node for the expression being switched on
kind int // kind of switch statement (switchKind*)
} }
func ordlcmp(c1 *Case, c2 *Case) int { // A typeSwitch walks a type switch.
// sort default first type typeSwitch struct {
if c1.type_ == Tdefault { hashname *Node // node for the hash of the type of the variable being switched on
return -1 facename *Node // node for the concrete type of the variable being switched on
} okname *Node // boolean node used for comma-ok type assertions
if c2.type_ == Tdefault {
return +1
}
// sort nil second
if c1.type_ == Ttypenil {
return -1
}
if c2.type_ == Ttypenil {
return +1
}
// sort by ordinal
if c1.ordinal > c2.ordinal {
return +1
}
if c1.ordinal < c2.ordinal {
return -1
}
return 0
} }
func exprcmp(c1 *Case, c2 *Case) int { // A caseClause is a single case clause in a switch statement.
// sort non-constants last type caseClause struct {
if c1.type_ != Texprconst { node *Node // points at case statement
return +1 ordinal int // position in switch
} hash uint32 // hash of a type switch
if c2.type_ != Texprconst { typ uint8 // type of case
return -1 }
}
n1 := c1.node.Left // typecheckswitch typechecks a switch statement.
n2 := c2.node.Left func typecheckswitch(n *Node) {
lno := int(lineno)
typechecklist(n.Ninit, Etop)
// sort by type (for switches on interface) var nilonly string
ct := int(n1.Val.Ctype) var top int
var t *Type
if ct != int(n2.Val.Ctype) { if n.Ntest != nil && n.Ntest.Op == OTYPESW {
return ct - int(n2.Val.Ctype) // type switch
} top = Etype
if !Eqtype(n1.Type, n2.Type) { typecheck(&n.Ntest.Right, Erv)
if n1.Type.Vargen > n2.Type.Vargen { t = n.Ntest.Right.Type
return +1 if t != nil && t.Etype != TINTER {
Yyerror("cannot type switch on non-interface value %v", Nconv(n.Ntest.Right, obj.FmtLong))
}
} else {
// expression switch
top = Erv
if n.Ntest != nil {
typecheck(&n.Ntest, Erv)
defaultlit(&n.Ntest, nil)
t = n.Ntest.Type
} else { } else {
return -1 t = Types[TBOOL]
}
if t != nil {
var badtype *Type
switch {
case okforeq[t.Etype] == 0:
Yyerror("cannot switch on %v", Nconv(n.Ntest, obj.FmtLong))
case t.Etype == TARRAY && !Isfixedarray(t):
nilonly = "slice"
case t.Etype == TARRAY && Isfixedarray(t) && algtype1(t, nil) == ANOEQ:
Yyerror("cannot switch on %v", Nconv(n.Ntest, obj.FmtLong))
case t.Etype == TSTRUCT && algtype1(t, &badtype) == ANOEQ:
Yyerror("cannot switch on %v (struct containing %v cannot be compared)", Nconv(n.Ntest, obj.FmtLong), Tconv(badtype, 0))
case t.Etype == TFUNC:
nilonly = "func"
case t.Etype == TMAP:
nilonly = "map"
}
} }
} }
// sort by constant value n.Type = t
n := 0
switch ct { var def *Node
case CTFLT: var ll *NodeList
n = mpcmpfltflt(n1.Val.U.Fval, n2.Val.U.Fval) for l := n.List; l != nil; l = l.Next {
ncase := l.N
setlineno(n)
if ncase.List == nil {
// default
if def != nil {
Yyerror("multiple defaults in switch (first at %v)", def.Line())
} else {
def = ncase
}
} else {
for ll = ncase.List; ll != nil; ll = ll.Next {
setlineno(ll.N)
typecheck(&ll.N, Erv|Etype)
if ll.N.Type == nil || t == nil {
continue
}
setlineno(ncase)
switch top {
// expression switch
case Erv:
defaultlit(&ll.N, t)
switch {
case ll.N.Op == OTYPE:
Yyerror("type %v is not an expression", Tconv(ll.N.Type, 0))
case ll.N.Type != nil && assignop(ll.N.Type, t, nil) == 0 && assignop(t, ll.N.Type, nil) == 0:
if n.Ntest != nil {
Yyerror("invalid case %v in switch on %v (mismatched types %v and %v)", Nconv(ll.N, 0), Nconv(n.Ntest, 0), Tconv(ll.N.Type, 0), Tconv(t, 0))
} else {
Yyerror("invalid case %v in switch (mismatched types %v and bool)", Nconv(ll.N, 0), Tconv(ll.N.Type, 0))
}
case nilonly != "" && !Isconst(ll.N, CTNIL):
Yyerror("invalid case %v in switch (can only compare %s %v to nil)", Nconv(ll.N, 0), nilonly, Nconv(n.Ntest, 0))
}
// type switch
case Etype:
var missing, have *Type
var ptr int
switch {
case ll.N.Op == OLITERAL && Istype(ll.N.Type, TNIL):
case ll.N.Op != OTYPE && ll.N.Type != nil: // should this be ||?
Yyerror("%v is not a type", Nconv(ll.N, obj.FmtLong))
// reset to original type
ll.N = n.Ntest.Right
case ll.N.Type.Etype != TINTER && t.Etype == TINTER && !implements(ll.N.Type, t, &missing, &have, &ptr):
if have != nil && missing.Broke == 0 && have.Broke == 0 {
Yyerror("impossible type switch case: %v cannot have dynamic type %v"+" (wrong type for %v method)\n\thave %v%v\n\twant %v%v", Nconv(n.Ntest.Right, obj.FmtLong), Tconv(ll.N.Type, 0), Sconv(missing.Sym, 0), Sconv(have.Sym, 0), Tconv(have.Type, obj.FmtShort), Sconv(missing.Sym, 0), Tconv(missing.Type, obj.FmtShort))
} else if missing.Broke == 0 {
Yyerror("impossible type switch case: %v cannot have dynamic type %v"+" (missing %v method)", Nconv(n.Ntest.Right, obj.FmtLong), Tconv(ll.N.Type, 0), Sconv(missing.Sym, 0))
}
}
}
}
}
if top == Etype && n.Type != nil {
ll = ncase.List
nvar := ncase.Nname
if nvar != nil {
if ll != nil && ll.Next == nil && ll.N.Type != nil && !Istype(ll.N.Type, TNIL) {
// single entry type switch
nvar.Ntype = typenod(ll.N.Type)
} else {
// multiple entry type switch or default
nvar.Ntype = typenod(n.Type)
}
case CTINT, typecheck(&nvar, Erv|Easgn)
CTRUNE: ncase.Nname = nvar
n = Mpcmpfixfix(n1.Val.U.Xval, n2.Val.U.Xval) }
}
case CTSTR: typechecklist(ncase.Nbody, Etop)
n = cmpslit(n1, n2)
} }
return n lineno = int32(lno)
} }
func typecmp(c1 *Case, c2 *Case) int { // walkswitch walks a switch statement.
// sort non-constants last func walkswitch(sw *Node) {
if c1.type_ != Ttypeconst { // convert switch {...} to switch true {...}
return +1 if sw.Ntest == nil {
} sw.Ntest = Nodbool(true)
if c2.type_ != Ttypeconst { typecheck(&sw.Ntest, Erv)
return -1
} }
// sort by hash code if sw.Ntest.Op == OTYPESW {
if c1.hash > c2.hash { var s typeSwitch
return +1 s.walk(sw)
} } else {
if c1.hash < c2.hash { var s exprSwitch
return -1 s.walk(sw)
} }
// sort by ordinal so duplicate error // Discard old AST elements. They can confuse racewalk.
// happens on later case. sw.Ntest = nil
if c1.ordinal > c2.ordinal { sw.List = nil
return +1
}
if c1.ordinal < c2.ordinal {
return -1
}
return 0
} }
func csort(l *Case, f func(*Case, *Case) int) *Case { // walk generates an AST implementing sw.
if l == nil || l.link == nil { // sw is an expression switch.
return l // The AST is generally of the form of a linear
} // search using if..goto, although binary search
// is used with long runs of constants.
func (s *exprSwitch) walk(sw *Node) {
casebody(sw, nil)
l1 := l s.kind = switchKindExpr
l2 := l if Isconst(sw.Ntest, CTBOOL) {
for { s.kind = switchKindTrue
l2 = l2.link if sw.Ntest.Val.U.Bval == 0 {
if l2 == nil { s.kind = switchKindFalse
break
}
l2 = l2.link
if l2 == nil {
break
} }
l1 = l1.link
} }
l2 = l1.link walkexpr(&sw.Ntest, &sw.Ninit)
l1.link = nil t := sw.Type
l1 = csort(l, f) if t == nil {
l2 = csort(l2, f) return
}
/* set up lead element */ // convert the switch into OIF statements
if f(l1, l2) < 0 { var cas *NodeList
l = l1 if s.kind == switchKindTrue || s.kind == switchKindFalse {
l1 = l1.link s.exprname = Nodbool(s.kind == switchKindTrue)
} else if consttype(sw.Ntest) >= 0 {
// leave constants to enable dead code elimination (issue 9608)
s.exprname = sw.Ntest
} else { } else {
l = l2 s.exprname = temp(sw.Ntest.Type)
l2 = l2.link cas = list1(Nod(OAS, s.exprname, sw.Ntest))
typechecklist(cas, Etop)
} }
le := l // enumerate the cases, and lop off the default case
cc := caseClauses(sw, s.kind)
for { var def *Node
if l1 == nil { if len(cc) > 0 && cc[0].typ == caseKindDefault {
for l2 != nil { def = cc[0].node.Right
le.link = l2 cc = cc[1:]
le = l2 } else {
l2 = l2.link def = Nod(OBREAK, nil, nil)
} }
le.link = nil // handle the cases in order
break for len(cc) > 0 {
// deal with expressions one at a time
if okforcmp[t.Etype] == 0 || cc[0].typ != caseKindExprConst {
a := s.walkCases(cc[:1])
cas = list(cas, a)
cc = cc[1:]
continue
} }
if l2 == nil { // do binary search on runs of constants
for l1 != nil { var run int
le.link = l1 for run = 1; run < len(cc) && cc[run].typ == caseKindExprConst; run++ {
le = l1
l1 = l1.link
}
break
} }
if f(l1, l2) < 0 { // sort and compile constants
le.link = l1 sort.Sort(caseClauseByExpr(cc[:run]))
le = l1 a := s.walkCases(cc[:run])
l1 = l1.link cas = list(cas, a)
} else { cc = cc[run:]
le.link = l2
le = l2
l2 = l2.link
}
} }
le.link = nil // handle default case
return l if nerrors == 0 {
cas = list(cas, def)
sw.Nbody = concat(cas, sw.Nbody)
sw.List = nil
walkstmtlist(sw.Nbody)
}
} }
var newlabel_swt_label int // walkCases generates an AST implementing the cases in cc.
func (s *exprSwitch) walkCases(cc []*caseClause) *Node {
if len(cc) < binarySearchMin {
// linear search
var cas *NodeList
for _, c := range cc {
n := c.node
lno := int(setlineno(n))
a := Nod(OIF, nil, nil)
if (s.kind != switchKindTrue && s.kind != switchKindFalse) || assignop(n.Left.Type, s.exprname.Type, nil) == OCONVIFACE || assignop(s.exprname.Type, n.Left.Type, nil) == OCONVIFACE {
a.Ntest = Nod(OEQ, s.exprname, n.Left) // if name == val
typecheck(&a.Ntest, Erv)
} else if s.kind == switchKindTrue {
a.Ntest = n.Left // if val
} else {
// s.kind == switchKindFalse
a.Ntest = Nod(ONOT, n.Left, nil) // if !val
typecheck(&a.Ntest, Erv)
}
a.Nbody = list1(n.Right) // goto l
cas = list(cas, a)
lineno = int32(lno)
}
return liststmt(cas)
}
func newlabel_swt() *Node { // find the middle and recur
newlabel_swt_label++ half := len(cc) / 2
namebuf = fmt.Sprintf("%.6d", newlabel_swt_label) a := Nod(OIF, nil, nil)
return newname(Lookup(namebuf)) a.Ntest = Nod(OLE, s.exprname, cc[half-1].node.Left)
typecheck(&a.Ntest, Erv)
a.Nbody = list1(s.walkCases(cc[:half]))
a.Nelse = list1(s.walkCases(cc[half:]))
return a
} }
/* // casebody builds separate lists of statements and cases.
* build separate list of statements and cases // It makes labels between cases and statements
* make labels between cases and statements // and deals with fallthrough, break, and unreachable statements.
* deal with fallthrough, break, unreachable statements
*/
func casebody(sw *Node, typeswvar *Node) { func casebody(sw *Node, typeswvar *Node) {
if sw.List == nil { if sw.List == nil {
return return
...@@ -265,67 +331,54 @@ func casebody(sw *Node, typeswvar *Node) { ...@@ -265,67 +331,54 @@ func casebody(sw *Node, typeswvar *Node) {
lno := setlineno(sw) lno := setlineno(sw)
cas := (*NodeList)(nil) // cases var cas *NodeList // cases
stat := (*NodeList)(nil) // statements var stat *NodeList // statements
def := (*Node)(nil) // defaults var def *Node // defaults
br := Nod(OBREAK, nil, nil) br := Nod(OBREAK, nil, nil)
var c *Node
var go_ *Node
var needvar bool
var lc *NodeList
var last *Node
var n *Node
for l := sw.List; l != nil; l = l.Next { for l := sw.List; l != nil; l = l.Next {
n = l.N n := l.N
setlineno(n) setlineno(n)
if n.Op != OXCASE { if n.Op != OXCASE {
Fatal("casebody %v", Oconv(int(n.Op), 0)) Fatal("casebody %v", Oconv(int(n.Op), 0))
} }
n.Op = OCASE n.Op = OCASE
needvar = count(n.List) != 1 || n.List.N.Op == OLITERAL needvar := count(n.List) != 1 || n.List.N.Op == OLITERAL
go_ = Nod(OGOTO, newlabel_swt(), nil) jmp := Nod(OGOTO, newCaseLabel(), nil)
if n.List == nil { if n.List == nil {
if def != nil { if def != nil {
Yyerror("more than one default case") Yyerror("more than one default case")
} }
// reuse original default case // reuse original default case
n.Right = go_ n.Right = jmp
def = n def = n
} }
if n.List != nil && n.List.Next == nil { if n.List != nil && n.List.Next == nil {
// one case - reuse OCASE node. // one case -- reuse OCASE node
c = n.List.N n.Left = n.List.N
n.Right = jmp
n.Left = c
n.Right = go_
n.List = nil n.List = nil
cas = list(cas, n) cas = list(cas, n)
} else { } else {
// expand multi-valued cases // expand multi-valued cases
for lc = n.List; lc != nil; lc = lc.Next { for lc := n.List; lc != nil; lc = lc.Next {
c = lc.N cas = list(cas, Nod(OCASE, lc.N, jmp))
cas = list(cas, Nod(OCASE, c, go_))
} }
} }
stat = list(stat, Nod(OLABEL, go_.Left, nil)) stat = list(stat, Nod(OLABEL, jmp.Left, nil))
if typeswvar != nil && needvar && n.Nname != nil { if typeswvar != nil && needvar && n.Nname != nil {
l := list1(Nod(ODCL, n.Nname, nil)) l := list1(Nod(ODCL, n.Nname, nil))
l = list(l, Nod(OAS, n.Nname, typeswvar)) l = list(l, Nod(OAS, n.Nname, typeswvar))
typechecklist(l, Etop) typechecklist(l, Etop)
stat = concat(stat, l) stat = concat(stat, l)
} }
stat = concat(stat, n.Nbody) stat = concat(stat, n.Nbody)
// botch - shouldn't fall thru declaration // botch - shouldn't fall thru declaration
last = stat.End.N last := stat.End.N
if last.Xoffset == n.Xoffset && last.Op == OXFALL { if last.Xoffset == n.Xoffset && last.Op == OXFALL {
if typeswvar != nil { if typeswvar != nil {
setlineno(last) setlineno(last)
...@@ -353,326 +406,103 @@ func casebody(sw *Node, typeswvar *Node) { ...@@ -353,326 +406,103 @@ func casebody(sw *Node, typeswvar *Node) {
lineno = lno lineno = lno
} }
func mkcaselist(sw *Node, arg int) *Case { // nSwitchLabel is the number of switch labels generated.
var n *Node // This should be per-function, but it is a global counter for now.
var c1 *Case var nSwitchLabel int
c := (*Case)(nil) func newCaseLabel() *Node {
ord := 0 label := strconv.Itoa(nSwitchLabel)
nSwitchLabel++
return newname(Lookup(label))
}
// caseClauses generates a slice of caseClauses
// corresponding to the clauses in the switch statement sw.
// Kind is the kind of switch statement.
func caseClauses(sw *Node, kind int) []*caseClause {
var cc []*caseClause
for l := sw.List; l != nil; l = l.Next { for l := sw.List; l != nil; l = l.Next {
n = l.N n := l.N
c1 = new(Case) c := new(caseClause)
c1.link = c cc = append(cc, c)
c = c1 c.ordinal = len(cc)
ord++
if int(uint16(ord)) != ord {
Fatal("too many cases in switch")
}
c.ordinal = uint16(ord)
c.node = n c.node = n
if n.Left == nil { if n.Left == nil {
c.type_ = Tdefault c.typ = caseKindDefault
continue continue
} }
switch arg { if kind == switchKindType {
case Stype: // type switch
c.hash = 0 switch {
if n.Left.Op == OLITERAL { case n.Left.Op == OLITERAL:
c.type_ = Ttypenil c.typ = caseKindTypeNil
continue case Istype(n.Left.Type, TINTER):
} c.typ = caseKindTypeVar
default:
if Istype(n.Left.Type, TINTER) { c.typ = caseKindTypeConst
c.type_ = Ttypevar c.hash = typehash(n.Left.Type)
continue
} }
} else {
c.hash = typehash(n.Left.Type) // expression switch
c.type_ = Ttypeconst
continue
case Snorm,
Strue,
Sfalse:
c.type_ = Texprvar
c.hash = typehash(n.Left.Type)
switch consttype(n.Left) { switch consttype(n.Left) {
case CTFLT, case CTFLT, CTINT, CTRUNE, CTSTR:
CTINT, c.typ = caseKindExprConst
CTRUNE, default:
CTSTR: c.typ = caseKindExprVar
c.type_ = Texprconst
} }
continue
} }
} }
if c == nil { if cc == nil {
return nil return nil
} }
// sort by value and diagnose duplicate cases // sort by value and diagnose duplicate cases
switch arg { if kind == switchKindType {
case Stype: // type switch
c = csort(c, typecmp) sort.Sort(caseClauseByType(cc))
var c2 *Case for i, c1 := range cc {
for c1 := c; c1 != nil; c1 = c1.link { if c1.typ == caseKindTypeNil || c1.typ == caseKindDefault {
for c2 = c1.link; c2 != nil && c2.hash == c1.hash; c2 = c2.link { break
if c1.type_ == Ttypenil || c1.type_ == Tdefault { }
break for _, c2 := range cc[i+1:] {
} if c2.typ == caseKindTypeNil || c2.typ == caseKindDefault || c1.hash != c2.hash {
if c2.type_ == Ttypenil || c2.type_ == Tdefault {
break break
} }
if !Eqtype(c1.node.Left.Type, c2.node.Left.Type) { if Eqtype(c1.node.Left.Type, c2.node.Left.Type) {
continue yyerrorl(int(c2.node.Lineno), "duplicate case %v in type switch\n\tprevious case at %v", Tconv(c2.node.Left.Type, 0), c1.node.Line())
} }
yyerrorl(int(c2.node.Lineno), "duplicate case %v in type switch\n\tprevious case at %v", Tconv(c2.node.Left.Type, 0), c1.node.Line())
} }
} }
} else {
case Snorm, // expression switch
Strue, sort.Sort(caseClauseByExpr(cc))
Sfalse: for i, c1 := range cc {
c = csort(c, exprcmp) if i+1 == len(cc) {
for c1 := c; c1.link != nil; c1 = c1.link { break
if exprcmp(c1, c1.link) != 0 { }
c2 := cc[i+1]
if exprcmp(c1, c2) != 0 {
continue continue
} }
setlineno(c1.link.node) setlineno(c2.node)
Yyerror("duplicate case %v in switch\n\tprevious case at %v", Nconv(c1.node.Left, 0), c1.node.Line()) Yyerror("duplicate case %v in switch\n\tprevious case at %v", Nconv(c1.node.Left, 0), c1.node.Line())
} }
} }
// put list back in processing order // put list back in processing order
c = csort(c, ordlcmp) sort.Sort(caseClauseByOrd(cc))
return cc
return c
} }
var exprname *Node // walk generates an AST that implements sw,
// where sw is a type switch.
func exprbsw(c0 *Case, ncase int, arg int) *Node { // The AST is generally of the form of a linear
cas := (*NodeList)(nil) // search using if..goto, although binary search
if ncase < Ncase { // is used with long runs of concrete types.
var a *Node func (s *typeSwitch) walk(sw *Node) {
var n *Node
var lno int
for i := 0; i < ncase; i++ {
n = c0.node
lno = int(setlineno(n))
if (arg != Strue && arg != Sfalse) || assignop(n.Left.Type, exprname.Type, nil) == OCONVIFACE || assignop(exprname.Type, n.Left.Type, nil) == OCONVIFACE {
a = Nod(OIF, nil, nil)
a.Ntest = Nod(OEQ, exprname, n.Left) // if name == val
typecheck(&a.Ntest, Erv)
a.Nbody = list1(n.Right) // then goto l
} else if arg == Strue {
a = Nod(OIF, nil, nil)
a.Ntest = n.Left // if val
a.Nbody = list1(n.Right) // then goto l // arg == Sfalse
} else {
a = Nod(OIF, nil, nil)
a.Ntest = Nod(ONOT, n.Left, nil) // if !val
typecheck(&a.Ntest, Erv)
a.Nbody = list1(n.Right) // then goto l
}
cas = list(cas, a)
c0 = c0.link
lineno = int32(lno)
}
return liststmt(cas)
}
// find the middle and recur
c := c0
half := ncase >> 1
for i := 1; i < half; i++ {
c = c.link
}
a := Nod(OIF, nil, nil)
a.Ntest = Nod(OLE, exprname, c.node.Left)
typecheck(&a.Ntest, Erv)
a.Nbody = list1(exprbsw(c0, half, arg))
a.Nelse = list1(exprbsw(c.link, ncase-half, arg))
return a
}
/*
* normal (expression) switch.
* rebuild case statements into if .. goto
*/
func exprswitch(sw *Node) {
casebody(sw, nil)
arg := Snorm
if Isconst(sw.Ntest, CTBOOL) {
arg = Strue
if sw.Ntest.Val.U.Bval == 0 {
arg = Sfalse
}
}
walkexpr(&sw.Ntest, &sw.Ninit)
t := sw.Type
if t == nil {
return
}
/*
* convert the switch into OIF statements
*/
exprname = nil
cas := (*NodeList)(nil)
if arg == Strue || arg == Sfalse {
exprname = Nodbool(arg == Strue)
} else if consttype(sw.Ntest) >= 0 {
// leave constants to enable dead code elimination (issue 9608)
exprname = sw.Ntest
} else {
exprname = temp(sw.Ntest.Type)
cas = list1(Nod(OAS, exprname, sw.Ntest))
typechecklist(cas, Etop)
}
c0 := mkcaselist(sw, arg)
var def *Node
if c0 != nil && c0.type_ == Tdefault {
def = c0.node.Right
c0 = c0.link
} else {
def = Nod(OBREAK, nil, nil)
}
var c *Case
var a *Node
var ncase int
var c1 *Case
loop:
if c0 == nil {
cas = list(cas, def)
sw.Nbody = concat(cas, sw.Nbody)
sw.List = nil
walkstmtlist(sw.Nbody)
return
}
// deal with the variables one-at-a-time
if okforcmp[t.Etype] == 0 || c0.type_ != Texprconst {
a = exprbsw(c0, 1, arg)
cas = list(cas, a)
c0 = c0.link
goto loop
}
// do binary search on run of constants
ncase = 1
for c = c0; c.link != nil; c = c.link {
if c.link.type_ != Texprconst {
break
}
ncase++
}
// break the chain at the count
c1 = c.link
c.link = nil
// sort and compile constants
c0 = csort(c0, exprcmp)
a = exprbsw(c0, ncase, arg)
cas = list(cas, a)
c0 = c1
goto loop
}
var hashname *Node
var facename *Node
var boolname *Node
func typeone(t *Node) *Node {
var_ := t.Nname
init := (*NodeList)(nil)
if var_ == nil {
typecheck(&nblank, Erv|Easgn)
var_ = nblank
} else {
init = list1(Nod(ODCL, var_, nil))
}
a := Nod(OAS2, nil, nil)
a.List = list(list1(var_), boolname) // var,bool =
b := Nod(ODOTTYPE, facename, nil)
b.Type = t.Left.Type // interface.(type)
a.Rlist = list1(b)
typecheck(&a, Etop)
init = list(init, a)
b = Nod(OIF, nil, nil)
b.Ntest = boolname
b.Nbody = list1(t.Right) // if bool { goto l }
a = liststmt(list(init, b))
return a
}
func typebsw(c0 *Case, ncase int) *Node {
cas := (*NodeList)(nil)
if ncase < Ncase {
var n *Node
var a *Node
for i := 0; i < ncase; i++ {
n = c0.node
if c0.type_ != Ttypeconst {
Fatal("typebsw")
}
a = Nod(OIF, nil, nil)
a.Ntest = Nod(OEQ, hashname, Nodintconst(int64(c0.hash)))
typecheck(&a.Ntest, Erv)
a.Nbody = list1(n.Right)
cas = list(cas, a)
c0 = c0.link
}
return liststmt(cas)
}
// find the middle and recur
c := c0
half := ncase >> 1
for i := 1; i < half; i++ {
c = c.link
}
a := Nod(OIF, nil, nil)
a.Ntest = Nod(OLE, hashname, Nodintconst(int64(c.hash)))
typecheck(&a.Ntest, Erv)
a.Nbody = list1(typebsw(c0, half))
a.Nelse = list1(typebsw(c.link, ncase-half))
return a
}
/*
* convert switch of the form
* switch v := i.(type) { case t1: ..; case t2: ..; }
* into if statements
*/
func typeswitch(sw *Node) {
if sw.Ntest == nil { if sw.Ntest == nil {
return return
} }
...@@ -688,26 +518,25 @@ func typeswitch(sw *Node) { ...@@ -688,26 +518,25 @@ func typeswitch(sw *Node) {
return return
} }
cas := (*NodeList)(nil) var cas *NodeList
/* // predeclare temporary variables and the boolean var
* predeclare temporary variables s.facename = temp(sw.Ntest.Right.Type)
* and the boolean var
*/
facename = temp(sw.Ntest.Right.Type)
a := Nod(OAS, facename, sw.Ntest.Right) a := Nod(OAS, s.facename, sw.Ntest.Right)
typecheck(&a, Etop) typecheck(&a, Etop)
cas = list(cas, a) cas = list(cas, a)
casebody(sw, facename) s.okname = temp(Types[TBOOL])
typecheck(&s.okname, Erv)
boolname = temp(Types[TBOOL]) s.hashname = temp(Types[TUINT32])
typecheck(&boolname, Erv) typecheck(&s.hashname, Erv)
hashname = temp(Types[TUINT32]) // set up labels and jumps
typecheck(&hashname, Erv) casebody(sw, s.facename)
// calculate type hash
t := sw.Ntest.Right.Type t := sw.Ntest.Right.Type
if isnilinter(t) { if isnilinter(t) {
a = syslook("efacethash", 1) a = syslook("efacethash", 1)
...@@ -716,98 +545,81 @@ func typeswitch(sw *Node) { ...@@ -716,98 +545,81 @@ func typeswitch(sw *Node) {
} }
argtype(a, t) argtype(a, t)
a = Nod(OCALL, a, nil) a = Nod(OCALL, a, nil)
a.List = list1(facename) a.List = list1(s.facename)
a = Nod(OAS, hashname, a) a = Nod(OAS, s.hashname, a)
typecheck(&a, Etop) typecheck(&a, Etop)
cas = list(cas, a) cas = list(cas, a)
c0 := mkcaselist(sw, Stype) cc := caseClauses(sw, switchKindType)
var def *Node var def *Node
if c0 != nil && c0.type_ == Tdefault { if len(cc) > 0 && cc[0].typ == caseKindDefault {
def = c0.node.Right def = cc[0].node.Right
c0 = c0.link cc = cc[1:]
} else { } else {
def = Nod(OBREAK, nil, nil) def = Nod(OBREAK, nil, nil)
} }
/* // insert type equality check into each case block
* insert if statement into each case block for _, c := range cc {
*/ n := c.node
var v Val switch c.typ {
var n *Node case caseKindTypeNil:
for c := c0; c != nil; c = c.link { var v Val
n = c.node
switch c.type_ {
case Ttypenil:
v.Ctype = CTNIL v.Ctype = CTNIL
a = Nod(OIF, nil, nil) a = Nod(OIF, nil, nil)
a.Ntest = Nod(OEQ, facename, nodlit(v)) a.Ntest = Nod(OEQ, s.facename, nodlit(v))
typecheck(&a.Ntest, Erv) typecheck(&a.Ntest, Erv)
a.Nbody = list1(n.Right) // if i==nil { goto l } a.Nbody = list1(n.Right) // if i==nil { goto l }
n.Right = a n.Right = a
case Ttypevar, case caseKindTypeVar, caseKindTypeConst:
Ttypeconst: n.Right = s.typeone(n)
n.Right = typeone(n)
} }
} }
/* // generate list of if statements, binary search for constant sequences
* generate list of if statements, binary search for constant sequences for len(cc) > 0 {
*/ if cc[0].typ != caseKindTypeConst {
var ncase int n := cc[0].node
var c1 *Case
var hash *NodeList
var c *Case
for c0 != nil {
if c0.type_ != Ttypeconst {
n = c0.node
cas = list(cas, n.Right) cas = list(cas, n.Right)
c0 = c0.link cc = cc[1:]
continue continue
} }
// identify run of constants // identify run of constants
c = c0 var run int
c1 = c for run = 1; run < len(cc) && cc[run].typ == caseKindTypeConst; run++ {
for c.link != nil && c.link.type_ == Ttypeconst {
c = c.link
} }
c0 = c.link
c.link = nil
// sort by hash // sort by hash
c1 = csort(c1, typecmp) sort.Sort(caseClauseByType(cc[:run]))
// for debugging: linear search // for debugging: linear search
if false { if false {
for c = c1; c != nil; c = c.link { for i := 0; i < run; i++ {
n = c.node n := cc[i].node
cas = list(cas, n.Right) cas = list(cas, n.Right)
} }
continue continue
} }
// combine adjacent cases with the same hash // combine adjacent cases with the same hash
ncase = 0 ncase := 0
for i := 0; i < run; i++ {
for c = c1; c != nil; c = c.link {
ncase++ ncase++
hash = list1(c.node.Right) hash := list1(cc[i].node.Right)
for c.link != nil && c.link.hash == c.hash { for j := i + 1; j < run && cc[i].hash == cc[j].hash; j++ {
hash = list(hash, c.link.node.Right) hash = list(hash, cc[j].node.Right)
c.link = c.link.link
} }
cc[i].node.Right = liststmt(hash)
c.node.Right = liststmt(hash)
} }
// binary search among cases to narrow by hash // binary search among cases to narrow by hash
cas = list(cas, typebsw(c1, ncase)) cas = list(cas, s.walkCases(cc[:ncase]))
cc = cc[ncase:]
} }
// handle default case
if nerrors == 0 { if nerrors == 0 {
cas = list(cas, def) cas = list(cas, def)
sw.Nbody = concat(cas, sw.Nbody) sw.Nbody = concat(cas, sw.Nbody)
...@@ -816,161 +628,192 @@ func typeswitch(sw *Node) { ...@@ -816,161 +628,192 @@ func typeswitch(sw *Node) {
} }
} }
func walkswitch(sw *Node) { // typeone generates an AST that jumps to the
/* // case body if the variable is of type t.
* reorder the body into (OLIST, cases, statements) func (s *typeSwitch) typeone(t *Node) *Node {
* cases have OGOTO into statements. name := t.Nname
* both have inserted OBREAK statements var init *NodeList
*/ if name == nil {
if sw.Ntest == nil { typecheck(&nblank, Erv|Easgn)
sw.Ntest = Nodbool(true) name = nblank
typecheck(&sw.Ntest, Erv) } else {
init = list1(Nod(ODCL, name, nil))
} }
if sw.Ntest.Op == OTYPESW { a := Nod(OAS2, nil, nil)
typeswitch(sw) a.List = list(list1(name), s.okname) // name, ok =
b := Nod(ODOTTYPE, s.facename, nil)
b.Type = t.Left.Type // interface.(type)
a.Rlist = list1(b)
typecheck(&a, Etop)
init = list(init, a)
//dump("sw", sw); c := Nod(OIF, nil, nil)
return c.Ntest = s.okname
c.Nbody = list1(t.Right) // if ok { goto l }
return liststmt(list(init, c))
}
// walkCases generates an AST implementing the cases in cc.
func (s *typeSwitch) walkCases(cc []*caseClause) *Node {
if len(cc) < binarySearchMin {
var cas *NodeList
for _, c := range cc {
n := c.node
if c.typ != caseKindTypeConst {
Fatal("typeSwitch walkCases")
}
a := Nod(OIF, nil, nil)
a.Ntest = Nod(OEQ, s.hashname, Nodintconst(int64(c.hash)))
typecheck(&a.Ntest, Erv)
a.Nbody = list1(n.Right)
cas = list(cas, a)
}
return liststmt(cas)
} }
exprswitch(sw) // find the middle and recur
half := len(cc) / 2
a := Nod(OIF, nil, nil)
a.Ntest = Nod(OLE, s.hashname, Nodintconst(int64(cc[half-1].hash)))
typecheck(&a.Ntest, Erv)
a.Nbody = list1(s.walkCases(cc[:half]))
a.Nelse = list1(s.walkCases(cc[half:]))
return a
}
// Discard old AST elements after a walk. They can confuse racewealk. type caseClauseByOrd []*caseClause
sw.Ntest = nil
sw.List = nil func (x caseClauseByOrd) Len() int { return len(x) }
func (x caseClauseByOrd) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x caseClauseByOrd) Less(i, j int) bool {
c1, c2 := x[i], x[j]
switch {
// sort default first
case c1.typ == caseKindDefault:
return true
case c2.typ == caseKindDefault:
return false
// sort nil second
case c1.typ == caseKindTypeNil:
return true
case c2.typ == caseKindTypeNil:
return false
}
// sort by ordinal
return c1.ordinal < c2.ordinal
} }
/* type caseClauseByExpr []*caseClause
* type check switch statement
*/
func typecheckswitch(n *Node) {
var top int
var t *Type
lno := int(lineno) func (x caseClauseByExpr) Len() int { return len(x) }
typechecklist(n.Ninit, Etop) func (x caseClauseByExpr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
nilonly := "" func (x caseClauseByExpr) Less(i, j int) bool {
return exprcmp(x[i], x[j]) < 0
}
if n.Ntest != nil && n.Ntest.Op == OTYPESW { func exprcmp(c1, c2 *caseClause) int {
// type switch // sort non-constants last
top = Etype if c1.typ != caseKindExprConst {
return +1
}
if c2.typ != caseKindExprConst {
return -1
}
typecheck(&n.Ntest.Right, Erv) n1 := c1.node.Left
t = n.Ntest.Right.Type n2 := c2.node.Left
if t != nil && t.Etype != TINTER {
Yyerror("cannot type switch on non-interface value %v", Nconv(n.Ntest.Right, obj.FmtLong))
}
} else {
// value switch
top = Erv
if n.Ntest != nil { // sort by type (for switches on interface)
typecheck(&n.Ntest, Erv) ct := int(n1.Val.Ctype)
defaultlit(&n.Ntest, nil) if ct > int(n2.Val.Ctype) {
t = n.Ntest.Type return +1
}
if ct < int(n2.Val.Ctype) {
return -1
}
if !Eqtype(n1.Type, n2.Type) {
if n1.Type.Vargen > n2.Type.Vargen {
return +1
} else { } else {
t = Types[TBOOL] return -1
}
if t != nil {
var badtype *Type
if okforeq[t.Etype] == 0 {
Yyerror("cannot switch on %v", Nconv(n.Ntest, obj.FmtLong))
} else if t.Etype == TARRAY && !Isfixedarray(t) {
nilonly = "slice"
} else if t.Etype == TARRAY && Isfixedarray(t) && algtype1(t, nil) == ANOEQ {
Yyerror("cannot switch on %v", Nconv(n.Ntest, obj.FmtLong))
} else if t.Etype == TSTRUCT && algtype1(t, &badtype) == ANOEQ {
Yyerror("cannot switch on %v (struct containing %v cannot be compared)", Nconv(n.Ntest, obj.FmtLong), Tconv(badtype, 0))
} else if t.Etype == TFUNC {
nilonly = "func"
} else if t.Etype == TMAP {
nilonly = "map"
}
} }
} }
n.Type = t // sort by constant value to enable binary search
switch ct {
case CTFLT:
return mpcmpfltflt(n1.Val.U.Fval, n2.Val.U.Fval)
case CTINT, CTRUNE:
return Mpcmpfixfix(n1.Val.U.Xval, n2.Val.U.Xval)
case CTSTR:
return cmpslit(n1, n2)
}
def := (*Node)(nil) return 0
var ptr int }
var have *Type
var nvar *Node
var ll *NodeList
var missing *Type
var ncase *Node
for l := n.List; l != nil; l = l.Next {
ncase = l.N
setlineno(n)
if ncase.List == nil {
// default
if def != nil {
Yyerror("multiple defaults in switch (first at %v)", def.Line())
} else {
def = ncase
}
} else {
for ll = ncase.List; ll != nil; ll = ll.Next {
setlineno(ll.N)
typecheck(&ll.N, Erv|Etype)
if ll.N.Type == nil || t == nil {
continue
}
setlineno(ncase)
switch top {
case Erv: // expression switch
defaultlit(&ll.N, t)
if ll.N.Op == OTYPE { type caseClauseByType []*caseClause
Yyerror("type %v is not an expression", Tconv(ll.N.Type, 0))
} else if ll.N.Type != nil && assignop(ll.N.Type, t, nil) == 0 && assignop(t, ll.N.Type, nil) == 0 {
if n.Ntest != nil {
Yyerror("invalid case %v in switch on %v (mismatched types %v and %v)", Nconv(ll.N, 0), Nconv(n.Ntest, 0), Tconv(ll.N.Type, 0), Tconv(t, 0))
} else {
Yyerror("invalid case %v in switch (mismatched types %v and bool)", Nconv(ll.N, 0), Tconv(ll.N.Type, 0))
}
} else if nilonly != "" && !Isconst(ll.N, CTNIL) {
Yyerror("invalid case %v in switch (can only compare %s %v to nil)", Nconv(ll.N, 0), nilonly, Nconv(n.Ntest, 0))
}
case Etype: // type switch func (x caseClauseByType) Len() int { return len(x) }
if ll.N.Op == OLITERAL && Istype(ll.N.Type, TNIL) { func (x caseClauseByType) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
} else if ll.N.Op != OTYPE && ll.N.Type != nil { // should this be ||? func (x caseClauseByType) Less(i, j int) bool {
Yyerror("%v is not a type", Nconv(ll.N, obj.FmtLong)) c1, c2 := x[i], x[j]
switch {
// sort non-constants last
case c1.typ != caseKindTypeConst:
return false
case c2.typ != caseKindTypeConst:
return true
// reset to original type // sort by hash code
ll.N = n.Ntest.Right case c1.hash != c2.hash:
} else if ll.N.Type.Etype != TINTER && t.Etype == TINTER && !implements(ll.N.Type, t, &missing, &have, &ptr) { return c1.hash < c2.hash
if have != nil && missing.Broke == 0 && have.Broke == 0 { }
Yyerror("impossible type switch case: %v cannot have dynamic type %v"+" (wrong type for %v method)\n\thave %v%v\n\twant %v%v", Nconv(n.Ntest.Right, obj.FmtLong), Tconv(ll.N.Type, 0), Sconv(missing.Sym, 0), Sconv(have.Sym, 0), Tconv(have.Type, obj.FmtShort), Sconv(missing.Sym, 0), Tconv(missing.Type, obj.FmtShort))
} else if missing.Broke == 0 {
Yyerror("impossible type switch case: %v cannot have dynamic type %v"+" (missing %v method)", Nconv(n.Ntest.Right, obj.FmtLong), Tconv(ll.N.Type, 0), Sconv(missing.Sym, 0))
}
}
}
}
}
if top == Etype && n.Type != nil { // sort by ordinal
ll = ncase.List return c1.ordinal < c2.ordinal
nvar = ncase.Nname }
if nvar != nil {
if ll != nil && ll.Next == nil && ll.N.Type != nil && !Istype(ll.N.Type, TNIL) {
// single entry type switch
nvar.Ntype = typenod(ll.N.Type)
} else {
// multiple entry type switch or default
nvar.Ntype = typenod(n.Type)
}
typecheck(&nvar, Erv|Easgn) func dumpcase(cc []*caseClause) {
ncase.Nname = nvar for _, c := range cc {
} switch c.typ {
} case caseKindDefault:
fmt.Printf("case-default\n")
fmt.Printf("\tord=%d\n", c.ordinal)
typechecklist(ncase.Nbody, Etop) case caseKindExprConst:
fmt.Printf("case-exprconst\n")
fmt.Printf("\tord=%d\n", c.ordinal)
case caseKindExprVar:
fmt.Printf("case-exprvar\n")
fmt.Printf("\tord=%d\n", c.ordinal)
fmt.Printf("\top=%v\n", Oconv(int(c.node.Left.Op), 0))
case caseKindTypeNil:
fmt.Printf("case-typenil\n")
fmt.Printf("\tord=%d\n", c.ordinal)
case caseKindTypeConst:
fmt.Printf("case-typeconst\n")
fmt.Printf("\tord=%d\n", c.ordinal)
fmt.Printf("\thash=%x\n", c.hash)
case caseKindTypeVar:
fmt.Printf("case-typevar\n")
fmt.Printf("\tord=%d\n", c.ordinal)
default:
fmt.Printf("case-???\n")
fmt.Printf("\tord=%d\n", c.ordinal)
fmt.Printf("\top=%v\n", Oconv(int(c.node.Left.Op), 0))
fmt.Printf("\thash=%x\n", c.hash)
}
} }
lineno = int32(lno) fmt.Printf("\n")
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment