import (
"cmd/internal/obj"
"fmt"
+ "sort"
+ "strconv"
)
const (
- Snorm = 0 + iota
- Strue
- Sfalse
- Stype
- 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)
+ // expression switch
+ switchKindExpr = iota // switch a {...} or switch 5 {...}
+ switchKindTrue // switch true {...} or switch {...}
+ switchKindFalse // switch false {...}
- case Texprvar:
- fmt.Printf("case-exprvar\n")
- fmt.Printf("\tord=%d\n", c.ordinal)
- fmt.Printf("\top=%v\n", Oconv(int(c.node.Left.Op), 0))
+ // type switch
+ switchKindType // switch a.(type) {...}
+)
- case Ttypenil:
- fmt.Printf("case-typenil\n")
- fmt.Printf("\tord=%d\n", c.ordinal)
+const (
+ caseKindDefault = iota // default:
- case Ttypeconst:
- fmt.Printf("case-typeconst\n")
- fmt.Printf("\tord=%d\n", c.ordinal)
- fmt.Printf("\thash=%x\n", c.hash)
+ // expression switch
+ caseKindExprConst // case 5:
+ caseKindExprVar // case x:
- case Ttypevar:
- fmt.Printf("case-typevar\n")
- fmt.Printf("\tord=%d\n", c.ordinal)
+ // type switch
+ caseKindTypeNil // case nil:
+ caseKindTypeConst // case time.Time: (concrete type, has type hash)
+ caseKindTypeVar // case io.Reader: (interface type)
+)
- 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)
- }
- }
+const binarySearchMin = 4 // minimum number of cases for binary search
- 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 {
- // sort default first
- if c1.type_ == Tdefault {
- return -1
- }
- 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
+// A typeSwitch walks a type switch.
+type typeSwitch struct {
+ hashname *Node // node for the hash of the type of the variable being switched on
+ facename *Node // node for the concrete type of the variable being switched on
+ okname *Node // boolean node used for comma-ok type assertions
}
-func exprcmp(c1 *Case, c2 *Case) int {
- // sort non-constants last
- if c1.type_ != Texprconst {
- return +1
- }
- if c2.type_ != Texprconst {
- return -1
- }
+// A caseClause is a single case clause in a switch statement.
+type caseClause struct {
+ node *Node // points at case statement
+ ordinal int // position in switch
+ hash uint32 // hash of a type switch
+ typ uint8 // type of case
+}
- n1 := c1.node.Left
- n2 := c2.node.Left
+// typecheckswitch typechecks a switch statement.
+func typecheckswitch(n *Node) {
+ lno := int(lineno)
+ typechecklist(n.Ninit, Etop)
- // sort by type (for switches on interface)
- ct := int(n1.Val.Ctype)
+ var nilonly string
+ var top int
+ var t *Type
- if ct != int(n2.Val.Ctype) {
- return ct - int(n2.Val.Ctype)
- }
- if !Eqtype(n1.Type, n2.Type) {
- if n1.Type.Vargen > n2.Type.Vargen {
- return +1
+ if n.Ntest != nil && n.Ntest.Op == OTYPESW {
+ // type switch
+ top = Etype
+ typecheck(&n.Ntest.Right, Erv)
+ t = n.Ntest.Right.Type
+ 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 {
- 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 := 0
+ n.Type = t
- switch ct {
- case CTFLT:
- n = mpcmpfltflt(n1.Val.U.Fval, n2.Val.U.Fval)
+ var def *Node
+ var ll *NodeList
+ 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,
- CTRUNE:
- n = Mpcmpfixfix(n1.Val.U.Xval, n2.Val.U.Xval)
+ typecheck(&nvar, Erv|Easgn)
+ ncase.Nname = nvar
+ }
+ }
- case CTSTR:
- n = cmpslit(n1, n2)
+ typechecklist(ncase.Nbody, Etop)
}
- return n
+ lineno = int32(lno)
}
-func typecmp(c1 *Case, c2 *Case) int {
- // sort non-constants last
- if c1.type_ != Ttypeconst {
- return +1
- }
- if c2.type_ != Ttypeconst {
- return -1
+// walkswitch walks a switch statement.
+func walkswitch(sw *Node) {
+ // convert switch {...} to switch true {...}
+ if sw.Ntest == nil {
+ sw.Ntest = Nodbool(true)
+ typecheck(&sw.Ntest, Erv)
}
- // sort by hash code
- if c1.hash > c2.hash {
- return +1
- }
- if c1.hash < c2.hash {
- return -1
+ if sw.Ntest.Op == OTYPESW {
+ var s typeSwitch
+ s.walk(sw)
+ } else {
+ var s exprSwitch
+ s.walk(sw)
}
- // sort by ordinal so duplicate error
- // happens on later case.
- if c1.ordinal > c2.ordinal {
- return +1
- }
- if c1.ordinal < c2.ordinal {
- return -1
- }
- return 0
+ // Discard old AST elements. They can confuse racewalk.
+ sw.Ntest = nil
+ sw.List = nil
}
-func csort(l *Case, f func(*Case, *Case) int) *Case {
- if l == nil || l.link == nil {
- return l
- }
+// walk generates an AST implementing sw.
+// sw is an expression switch.
+// 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
- l2 := l
- for {
- l2 = l2.link
- if l2 == nil {
- break
- }
- l2 = l2.link
- if l2 == nil {
- break
+ s.kind = switchKindExpr
+ if Isconst(sw.Ntest, CTBOOL) {
+ s.kind = switchKindTrue
+ if sw.Ntest.Val.U.Bval == 0 {
+ s.kind = switchKindFalse
}
- l1 = l1.link
}
- l2 = l1.link
- l1.link = nil
- l1 = csort(l, f)
- l2 = csort(l2, f)
+ walkexpr(&sw.Ntest, &sw.Ninit)
+ t := sw.Type
+ if t == nil {
+ return
+ }
- /* set up lead element */
- if f(l1, l2) < 0 {
- l = l1
- l1 = l1.link
+ // convert the switch into OIF statements
+ var cas *NodeList
+ if s.kind == switchKindTrue || s.kind == switchKindFalse {
+ 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 {
- l = l2
- l2 = l2.link
+ s.exprname = temp(sw.Ntest.Type)
+ cas = list1(Nod(OAS, s.exprname, sw.Ntest))
+ typechecklist(cas, Etop)
}
- le := l
-
- for {
- if l1 == nil {
- for l2 != nil {
- le.link = l2
- le = l2
- l2 = l2.link
- }
+ // enumerate the cases, and lop off the default case
+ cc := caseClauses(sw, s.kind)
+ var def *Node
+ if len(cc) > 0 && cc[0].typ == caseKindDefault {
+ def = cc[0].node.Right
+ cc = cc[1:]
+ } else {
+ def = Nod(OBREAK, nil, nil)
+ }
- le.link = nil
- break
+ // handle the cases in order
+ 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 {
- for l1 != nil {
- le.link = l1
- le = l1
- l1 = l1.link
- }
-
- break
+ // do binary search on runs of constants
+ var run int
+ for run = 1; run < len(cc) && cc[run].typ == caseKindExprConst; run++ {
}
- if f(l1, l2) < 0 {
- le.link = l1
- le = l1
- l1 = l1.link
- } else {
- le.link = l2
- le = l2
- l2 = l2.link
- }
+ // sort and compile constants
+ sort.Sort(caseClauseByExpr(cc[:run]))
+ a := s.walkCases(cc[:run])
+ cas = list(cas, a)
+ cc = cc[run:]
}
- le.link = nil
- return l
+ // handle default case
+ 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 {
- newlabel_swt_label++
- namebuf = fmt.Sprintf("%.6d", newlabel_swt_label)
- return newname(Lookup(namebuf))
+ // find the middle and recur
+ half := len(cc) / 2
+ a := Nod(OIF, nil, nil)
+ 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
}
-/*
- * build separate list of statements and cases
- * make labels between cases and statements
- * deal with fallthrough, break, unreachable statements
- */
+// casebody builds separate lists of statements and cases.
+// It makes labels between cases and statements
+// and deals with fallthrough, break, and unreachable statements.
func casebody(sw *Node, typeswvar *Node) {
if sw.List == nil {
return
lno := setlineno(sw)
- cas := (*NodeList)(nil) // cases
- stat := (*NodeList)(nil) // statements
- def := (*Node)(nil) // defaults
+ var cas *NodeList // cases
+ var stat *NodeList // statements
+ var def *Node // defaults
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 {
- n = l.N
+ n := l.N
setlineno(n)
if n.Op != OXCASE {
Fatal("casebody %v", Oconv(int(n.Op), 0))
}
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 def != nil {
Yyerror("more than one default case")
}
-
// reuse original default case
- n.Right = go_
-
+ n.Right = jmp
def = n
}
if n.List != nil && n.List.Next == nil {
- // one case - reuse OCASE node.
- c = n.List.N
-
- n.Left = c
- n.Right = go_
+ // one case -- reuse OCASE node
+ n.Left = n.List.N
+ n.Right = jmp
n.List = nil
cas = list(cas, n)
} else {
// expand multi-valued cases
- for lc = n.List; lc != nil; lc = lc.Next {
- c = lc.N
- cas = list(cas, Nod(OCASE, c, go_))
+ for lc := n.List; lc != nil; lc = lc.Next {
+ cas = list(cas, Nod(OCASE, lc.N, jmp))
}
}
- stat = list(stat, Nod(OLABEL, go_.Left, nil))
+ stat = list(stat, Nod(OLABEL, jmp.Left, nil))
if typeswvar != nil && needvar && n.Nname != nil {
l := list1(Nod(ODCL, n.Nname, nil))
l = list(l, Nod(OAS, n.Nname, typeswvar))
typechecklist(l, Etop)
stat = concat(stat, l)
}
-
stat = concat(stat, n.Nbody)
// botch - shouldn't fall thru declaration
- last = stat.End.N
-
+ last := stat.End.N
if last.Xoffset == n.Xoffset && last.Op == OXFALL {
if typeswvar != nil {
setlineno(last)
lineno = lno
}
-func mkcaselist(sw *Node, arg int) *Case {
- var n *Node
- var c1 *Case
+// nSwitchLabel is the number of switch labels generated.
+// This should be per-function, but it is a global counter for now.
+var nSwitchLabel int
- c := (*Case)(nil)
- ord := 0
+func newCaseLabel() *Node {
+ 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 {
- n = l.N
- c1 = new(Case)
- c1.link = c
- c = c1
-
- ord++
- if int(uint16(ord)) != ord {
- Fatal("too many cases in switch")
- }
- c.ordinal = uint16(ord)
+ n := l.N
+ c := new(caseClause)
+ cc = append(cc, c)
+ c.ordinal = len(cc)
c.node = n
if n.Left == nil {
- c.type_ = Tdefault
+ c.typ = caseKindDefault
continue
}
- switch arg {
- case Stype:
- c.hash = 0
- if n.Left.Op == OLITERAL {
- c.type_ = Ttypenil
- continue
- }
-
- if Istype(n.Left.Type, TINTER) {
- c.type_ = Ttypevar
- continue
+ if kind == switchKindType {
+ // type switch
+ switch {
+ case n.Left.Op == OLITERAL:
+ c.typ = caseKindTypeNil
+ case Istype(n.Left.Type, TINTER):
+ c.typ = caseKindTypeVar
+ default:
+ c.typ = caseKindTypeConst
+ c.hash = typehash(n.Left.Type)
}
-
- c.hash = typehash(n.Left.Type)
- c.type_ = Ttypeconst
- continue
-
- case Snorm,
- Strue,
- Sfalse:
- c.type_ = Texprvar
- c.hash = typehash(n.Left.Type)
+ } else {
+ // expression switch
switch consttype(n.Left) {
- case CTFLT,
- CTINT,
- CTRUNE,
- CTSTR:
- c.type_ = Texprconst
+ case CTFLT, CTINT, CTRUNE, CTSTR:
+ c.typ = caseKindExprConst
+ default:
+ c.typ = caseKindExprVar
}
-
- continue
}
}
- if c == nil {
+ if cc == nil {
return nil
}
// sort by value and diagnose duplicate cases
- switch arg {
- case Stype:
- c = csort(c, typecmp)
- var c2 *Case
- for c1 := c; c1 != nil; c1 = c1.link {
- for c2 = c1.link; c2 != nil && c2.hash == c1.hash; c2 = c2.link {
- if c1.type_ == Ttypenil || c1.type_ == Tdefault {
- break
- }
- if c2.type_ == Ttypenil || c2.type_ == Tdefault {
+ if kind == switchKindType {
+ // type switch
+ sort.Sort(caseClauseByType(cc))
+ for i, c1 := range cc {
+ if c1.typ == caseKindTypeNil || c1.typ == caseKindDefault {
+ break
+ }
+ for _, c2 := range cc[i+1:] {
+ if c2.typ == caseKindTypeNil || c2.typ == caseKindDefault || c1.hash != c2.hash {
break
}
- if !Eqtype(c1.node.Left.Type, c2.node.Left.Type) {
- continue
+ if Eqtype(c1.node.Left.Type, c2.node.Left.Type) {
+ 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())
}
}
-
- case Snorm,
- Strue,
- Sfalse:
- c = csort(c, exprcmp)
- for c1 := c; c1.link != nil; c1 = c1.link {
- if exprcmp(c1, c1.link) != 0 {
+ } else {
+ // expression switch
+ sort.Sort(caseClauseByExpr(cc))
+ for i, c1 := range cc {
+ if i+1 == len(cc) {
+ break
+ }
+ c2 := cc[i+1]
+ if exprcmp(c1, c2) != 0 {
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())
}
}
// put list back in processing order
- c = csort(c, ordlcmp)
-
- return c
+ sort.Sort(caseClauseByOrd(cc))
+ return cc
}
-var exprname *Node
-
-func exprbsw(c0 *Case, ncase int, arg int) *Node {
- cas := (*NodeList)(nil)
- if ncase < Ncase {
- var a *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) {
+// walk generates an AST that implements sw,
+// where sw is a type switch.
+// The AST is generally of the form of a linear
+// search using if..goto, although binary search
+// is used with long runs of concrete types.
+func (s *typeSwitch) walk(sw *Node) {
if sw.Ntest == nil {
return
}
return
}
- cas := (*NodeList)(nil)
+ var cas *NodeList
- /*
- * predeclare temporary variables
- * and the boolean var
- */
- facename = temp(sw.Ntest.Right.Type)
+ // predeclare temporary variables and the boolean var
+ s.facename = temp(sw.Ntest.Right.Type)
- a := Nod(OAS, facename, sw.Ntest.Right)
+ a := Nod(OAS, s.facename, sw.Ntest.Right)
typecheck(&a, Etop)
cas = list(cas, a)
- casebody(sw, facename)
+ s.okname = temp(Types[TBOOL])
+ typecheck(&s.okname, Erv)
- boolname = temp(Types[TBOOL])
- typecheck(&boolname, Erv)
+ s.hashname = temp(Types[TUINT32])
+ typecheck(&s.hashname, Erv)
- hashname = temp(Types[TUINT32])
- typecheck(&hashname, Erv)
+ // set up labels and jumps
+ casebody(sw, s.facename)
+ // calculate type hash
t := sw.Ntest.Right.Type
if isnilinter(t) {
a = syslook("efacethash", 1)
}
argtype(a, t)
a = Nod(OCALL, a, nil)
- a.List = list1(facename)
- a = Nod(OAS, hashname, a)
+ a.List = list1(s.facename)
+ a = Nod(OAS, s.hashname, a)
typecheck(&a, Etop)
cas = list(cas, a)
- c0 := mkcaselist(sw, Stype)
+ cc := caseClauses(sw, switchKindType)
var def *Node
- if c0 != nil && c0.type_ == Tdefault {
- def = c0.node.Right
- c0 = c0.link
+ if len(cc) > 0 && cc[0].typ == caseKindDefault {
+ def = cc[0].node.Right
+ cc = cc[1:]
} else {
def = Nod(OBREAK, nil, nil)
}
- /*
- * insert if statement into each case block
- */
- var v Val
- var n *Node
- for c := c0; c != nil; c = c.link {
- n = c.node
- switch c.type_ {
- case Ttypenil:
+ // insert type equality check into each case block
+ for _, c := range cc {
+ n := c.node
+ switch c.typ {
+ case caseKindTypeNil:
+ var v Val
v.Ctype = CTNIL
a = Nod(OIF, nil, nil)
- a.Ntest = Nod(OEQ, facename, nodlit(v))
+ a.Ntest = Nod(OEQ, s.facename, nodlit(v))
typecheck(&a.Ntest, Erv)
a.Nbody = list1(n.Right) // if i==nil { goto l }
n.Right = a
- case Ttypevar,
- Ttypeconst:
- n.Right = typeone(n)
+ case caseKindTypeVar, caseKindTypeConst:
+ n.Right = s.typeone(n)
}
}
- /*
- * generate list of if statements, binary search for constant sequences
- */
- var ncase int
- var c1 *Case
- var hash *NodeList
- var c *Case
- for c0 != nil {
- if c0.type_ != Ttypeconst {
- n = c0.node
+ // generate list of if statements, binary search for constant sequences
+ for len(cc) > 0 {
+ if cc[0].typ != caseKindTypeConst {
+ n := cc[0].node
cas = list(cas, n.Right)
- c0 = c0.link
+ cc = cc[1:]
continue
}
// identify run of constants
- c = c0
- c1 = c
-
- for c.link != nil && c.link.type_ == Ttypeconst {
- c = c.link
+ var run int
+ for run = 1; run < len(cc) && cc[run].typ == caseKindTypeConst; run++ {
}
- c0 = c.link
- c.link = nil
// sort by hash
- c1 = csort(c1, typecmp)
+ sort.Sort(caseClauseByType(cc[:run]))
// for debugging: linear search
if false {
- for c = c1; c != nil; c = c.link {
- n = c.node
+ for i := 0; i < run; i++ {
+ n := cc[i].node
cas = list(cas, n.Right)
}
-
continue
}
// combine adjacent cases with the same hash
- ncase = 0
-
- for c = c1; c != nil; c = c.link {
+ ncase := 0
+ for i := 0; i < run; i++ {
ncase++
- hash = list1(c.node.Right)
- for c.link != nil && c.link.hash == c.hash {
- hash = list(hash, c.link.node.Right)
- c.link = c.link.link
+ hash := list1(cc[i].node.Right)
+ for j := i + 1; j < run && cc[i].hash == cc[j].hash; j++ {
+ hash = list(hash, cc[j].node.Right)
}
-
- c.node.Right = liststmt(hash)
+ cc[i].node.Right = liststmt(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 {
cas = list(cas, def)
sw.Nbody = concat(cas, sw.Nbody)
}
}
-func walkswitch(sw *Node) {
- /*
- * reorder the body into (OLIST, cases, statements)
- * cases have OGOTO into statements.
- * both have inserted OBREAK statements
- */
- if sw.Ntest == nil {
- sw.Ntest = Nodbool(true)
- typecheck(&sw.Ntest, Erv)
+// typeone generates an AST that jumps to the
+// case body if the variable is of type t.
+func (s *typeSwitch) typeone(t *Node) *Node {
+ name := t.Nname
+ var init *NodeList
+ if name == nil {
+ typecheck(&nblank, Erv|Easgn)
+ name = nblank
+ } else {
+ init = list1(Nod(ODCL, name, nil))
}
- if sw.Ntest.Op == OTYPESW {
- typeswitch(sw)
+ a := Nod(OAS2, nil, nil)
+ 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);
- return
+ c := Nod(OIF, nil, nil)
+ 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.
- sw.Ntest = nil
+type caseClauseByOrd []*caseClause
- 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 check switch statement
- */
-func typecheckswitch(n *Node) {
- var top int
- var t *Type
+type caseClauseByExpr []*caseClause
- lno := int(lineno)
- typechecklist(n.Ninit, Etop)
- nilonly := ""
+func (x caseClauseByExpr) Len() int { return len(x) }
+func (x caseClauseByExpr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x caseClauseByExpr) Less(i, j int) bool {
+ return exprcmp(x[i], x[j]) < 0
+}
- if n.Ntest != nil && n.Ntest.Op == OTYPESW {
- // type switch
- top = Etype
+func exprcmp(c1, c2 *caseClause) int {
+ // sort non-constants last
+ if c1.typ != caseKindExprConst {
+ return +1
+ }
+ if c2.typ != caseKindExprConst {
+ return -1
+ }
- typecheck(&n.Ntest.Right, Erv)
- t = n.Ntest.Right.Type
- 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
+ n1 := c1.node.Left
+ n2 := c2.node.Left
- if n.Ntest != nil {
- typecheck(&n.Ntest, Erv)
- defaultlit(&n.Ntest, nil)
- t = n.Ntest.Type
+ // sort by type (for switches on interface)
+ ct := int(n1.Val.Ctype)
+ if ct > int(n2.Val.Ctype) {
+ 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 {
- t = Types[TBOOL]
- }
- 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"
- }
+ return -1
}
}
- 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)
- 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)
+ return 0
+}
- if ll.N.Op == OTYPE {
- 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))
- }
+type caseClauseByType []*caseClause
- case Etype: // type switch
- if ll.N.Op == OLITERAL && Istype(ll.N.Type, TNIL) {
- } else if ll.N.Op != OTYPE && ll.N.Type != nil { // should this be ||?
- Yyerror("%v is not a type", Nconv(ll.N, obj.FmtLong))
+func (x caseClauseByType) Len() int { return len(x) }
+func (x caseClauseByType) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x caseClauseByType) Less(i, j int) bool {
+ 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
- ll.N = n.Ntest.Right
- } else if 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))
- }
- }
- }
- }
- }
+ // sort by hash code
+ case c1.hash != c2.hash:
+ return c1.hash < c2.hash
+ }
- 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)
- }
+ // sort by ordinal
+ return c1.ordinal < c2.ordinal
+}
- typecheck(&nvar, Erv|Easgn)
- ncase.Nname = nvar
- }
- }
+func dumpcase(cc []*caseClause) {
+ 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")
}