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 // type switch
Texprvar switchKindType // switch a.(type) {...}
Ttypenil
Ttypeconst
Ttypevar
Ncase = 4
) )
type Case struct { const (
node *Node caseKindDefault = iota // default:
hash uint32
type_ uint8
diag uint8
ordinal uint16
link *Case
}
var C *Case // expression switch
caseKindExprConst // case 5:
caseKindExprVar // case x:
func dumpcase(c0 *Case) { // type switch
for c := c0; c != nil; c = c.link { caseKindTypeNil // case nil:
switch c.type_ { caseKindTypeConst // case time.Time: (concrete type, has type hash)
case Tdefault: caseKindTypeVar // case io.Reader: (interface type)
fmt.Printf("case-default\n") )
fmt.Printf("\tord=%d\n", c.ordinal)
case Texprconst: const binarySearchMin = 4 // minimum number of cases for binary search
fmt.Printf("case-exprconst\n")
fmt.Printf("\tord=%d\n", c.ordinal)
case Texprvar: // An exprSwitch walks an expression switch.
fmt.Printf("case-exprvar\n") type exprSwitch struct {
fmt.Printf("\tord=%d\n", c.ordinal) exprname *Node // node for the expression being switched on
fmt.Printf("\top=%v\n", Oconv(int(c.node.Left.Op), 0)) kind int // kind of switch statement (switchKind*)
}
case Ttypenil: // A typeSwitch walks a type switch.
fmt.Printf("case-typenil\n") type typeSwitch struct {
fmt.Printf("\tord=%d\n", c.ordinal) 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
}
case Ttypeconst: // A caseClause is a single case clause in a switch statement.
fmt.Printf("case-typeconst\n") type caseClause struct {
fmt.Printf("\tord=%d\n", c.ordinal) node *Node // points at case statement
fmt.Printf("\thash=%x\n", c.hash) ordinal int // position in switch
hash uint32 // hash of a type switch
typ uint8 // type of case
}
case Ttypevar: // typecheckswitch typechecks a switch statement.
fmt.Printf("case-typevar\n") func typecheckswitch(n *Node) {
fmt.Printf("\tord=%d\n", c.ordinal) lno := int(lineno)
typechecklist(n.Ninit, Etop)
default: var nilonly string
fmt.Printf("case-???\n") var top int
fmt.Printf("\tord=%d\n", c.ordinal) var t *Type
fmt.Printf("\top=%v\n", Oconv(int(c.node.Left.Op), 0))
fmt.Printf("\thash=%x\n", c.hash) 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 {
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"
} }
fmt.Printf("\n")
}
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 n.Type = t
if c1.type_ == Ttypenil {
return -1 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
} }
if c2.type_ == Ttypenil { } else {
return +1 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)
// sort by ordinal switch top {
if c1.ordinal > c2.ordinal { // expression switch
return +1 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))
} }
if c1.ordinal < c2.ordinal { case nilonly != "" && !Isconst(ll.N, CTNIL):
return -1 Yyerror("invalid case %v in switch (can only compare %s %v to nil)", Nconv(ll.N, 0), nilonly, Nconv(n.Ntest, 0))
} }
return 0
}
func exprcmp(c1 *Case, c2 *Case) int { // type switch
// sort non-constants last case Etype:
if c1.type_ != Texprconst { var missing, have *Type
return +1 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 c2.type_ != Texprconst {
return -1
} }
n1 := c1.node.Left
n2 := c2.node.Left
// sort by type (for switches on interface)
ct := int(n1.Val.Ctype)
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
} else {
return -1
} }
} }
// sort by constant value if top == Etype && n.Type != nil {
n := 0 ll = ncase.List
nvar := ncase.Nname
switch ct { if nvar != nil {
case CTFLT: if ll != nil && ll.Next == nil && ll.N.Type != nil && !Istype(ll.N.Type, TNIL) {
n = mpcmpfltflt(n1.Val.U.Fval, n2.Val.U.Fval) // 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)
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)
}
for { // handle the cases in order
if l1 == nil { for len(cc) > 0 {
for l2 != nil { // deal with expressions one at a time
le.link = l2 if okforcmp[t.Etype] == 0 || cc[0].typ != caseKindExprConst {
le = l2 a := s.walkCases(cc[:1])
l2 = l2.link cas = list(cas, a)
cc = cc[1:]
continue
} }
le.link = nil // do binary search on runs of constants
break var run int
for run = 1; run < len(cc) && cc[run].typ == caseKindExprConst; run++ {
} }
if l2 == nil { // sort and compile constants
for l1 != nil { sort.Sort(caseClauseByExpr(cc[:run]))
le.link = l1 a := s.walkCases(cc[:run])
le = l1 cas = list(cas, a)
l1 = l1.link cc = cc[run:]
} }
break // handle default case
if nerrors == 0 {
cas = list(cas, def)
sw.Nbody = concat(cas, sw.Nbody)
sw.List = nil
walkstmtlist(sw.Nbody)
} }
}
// 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))
if f(l1, l2) < 0 { a := Nod(OIF, nil, nil)
le.link = l1 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 {
le = l1 a.Ntest = Nod(OEQ, s.exprname, n.Left) // if name == val
l1 = l1.link typecheck(&a.Ntest, Erv)
} else if s.kind == switchKindTrue {
a.Ntest = n.Left // if val
} else { } else {
le.link = l2 // s.kind == switchKindFalse
le = l2 a.Ntest = Nod(ONOT, n.Left, nil) // if !val
l2 = l2.link typecheck(&a.Ntest, Erv)
}
} }
a.Nbody = list1(n.Right) // goto l
le.link = nil cas = list(cas, a)
return l lineno = int32(lno)
} }
return liststmt(cas)
var newlabel_swt_label int }
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,334 +406,111 @@ func casebody(sw *Node, typeswvar *Node) { ...@@ -353,334 +406,111 @@ 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
}
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 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 } else {
// expression switch
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 {
if c1.type_ == Ttypenil || c1.type_ == Tdefault {
break break
} }
if c2.type_ == Ttypenil || c2.type_ == Tdefault { for _, c2 := range cc[i+1:] {
if c2.typ == caseKindTypeNil || c2.typ == caseKindDefault || c1.hash != c2.hash {
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())
} }
} }
}
case Snorm, } else {
Strue, // expression switch
Sfalse: sort.Sort(caseClauseByExpr(cc))
c = csort(c, exprcmp) for i, c1 := range cc {
for c1 := c; c1.link != nil; c1 = c1.link { if i+1 == len(cc) {
if exprcmp(c1, c1.link) != 0 { break
}
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 if sw.Ntest == nil {
var lno int return
for i := 0; i < ncase; i++ { }
n = c0.node if sw.Ntest.Right == nil {
lno = int(setlineno(n)) setlineno(sw)
Yyerror("type switch must have an assignment")
if (arg != Strue && arg != Sfalse) || assignop(n.Left.Type, exprname.Type, nil) == OCONVIFACE || assignop(exprname.Type, n.Left.Type, nil) == OCONVIFACE { return
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 {
return
}
if sw.Ntest.Right == nil {
setlineno(sw)
Yyerror("type switch must have an assignment")
return
}
walkexpr(&sw.Ntest.Right, &sw.Ninit) walkexpr(&sw.Ntest.Right, &sw.Ninit)
if !Istype(sw.Ntest.Right.Type, TINTER) { if !Istype(sw.Ntest.Right.Type, TINTER) {
...@@ -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
switch c.typ {
case caseKindTypeNil:
var v Val var v Val
var n *Node
for c := c0; c != nil; c = c.link {
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 }
exprswitch(sw) return liststmt(list(init, c))
}
// Discard old AST elements after a walk. They can confuse racewealk. // walkCases generates an AST implementing the cases in cc.
sw.Ntest = nil 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)
}
sw.List = nil // 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
} }
/* type caseClauseByOrd []*caseClause
* type check switch statement
*/
func typecheckswitch(n *Node) {
var top int
var t *Type
lno := int(lineno) func (x caseClauseByOrd) Len() int { return len(x) }
typechecklist(n.Ninit, Etop) func (x caseClauseByOrd) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
nilonly := "" func (x caseClauseByOrd) Less(i, j int) bool {
c1, c2 := x[i], x[j]
if n.Ntest != nil && n.Ntest.Op == OTYPESW { switch {
// type switch // sort default first
top = Etype case c1.typ == caseKindDefault:
return true
case c2.typ == caseKindDefault:
return false
typecheck(&n.Ntest.Right, Erv) // sort nil second
t = n.Ntest.Right.Type case c1.typ == caseKindTypeNil:
if t != nil && t.Etype != TINTER { return true
Yyerror("cannot type switch on non-interface value %v", Nconv(n.Ntest.Right, obj.FmtLong)) case c2.typ == caseKindTypeNil:
return false
} }
} else {
// value switch
top = Erv
if n.Ntest != nil { // sort by ordinal
typecheck(&n.Ntest, Erv) return c1.ordinal < c2.ordinal
defaultlit(&n.Ntest, nil) }
t = n.Ntest.Type
} 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"
}
}
}
n.Type = t type caseClauseByExpr []*caseClause
def := (*Node)(nil) func (x caseClauseByExpr) Len() int { return len(x) }
var ptr int func (x caseClauseByExpr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
var have *Type func (x caseClauseByExpr) Less(i, j int) bool {
var nvar *Node return exprcmp(x[i], x[j]) < 0
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 { func exprcmp(c1, c2 *caseClause) int {
Yyerror("type %v is not an expression", Tconv(ll.N.Type, 0)) // sort non-constants last
} else if ll.N.Type != nil && assignop(ll.N.Type, t, nil) == 0 && assignop(t, ll.N.Type, nil) == 0 { if c1.typ != caseKindExprConst {
if n.Ntest != nil { return +1
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) { if c2.typ != caseKindExprConst {
Yyerror("invalid case %v in switch (can only compare %s %v to nil)", Nconv(ll.N, 0), nilonly, Nconv(n.Ntest, 0)) return -1
} }
case Etype: // type switch n1 := c1.node.Left
if ll.N.Op == OLITERAL && Istype(ll.N.Type, TNIL) { n2 := c2.node.Left
} else if 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 // sort by type (for switches on interface)
ll.N = n.Ntest.Right ct := int(n1.Val.Ctype)
} else if ll.N.Type.Etype != TINTER && t.Etype == TINTER && !implements(ll.N.Type, t, &missing, &have, &ptr) { if ct > int(n2.Val.Ctype) {
if have != nil && missing.Broke == 0 && have.Broke == 0 { return +1
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 ct < int(n2.Val.Ctype) {
return -1
} }
if !Eqtype(n1.Type, n2.Type) {
if n1.Type.Vargen > n2.Type.Vargen {
return +1
} else {
return -1
} }
} }
if top == Etype && n.Type != nil { // sort by constant value to enable binary search
ll = ncase.List switch ct {
nvar = ncase.Nname case CTFLT:
if nvar != nil { return mpcmpfltflt(n1.Val.U.Fval, n2.Val.U.Fval)
if ll != nil && ll.Next == nil && ll.N.Type != nil && !Istype(ll.N.Type, TNIL) { case CTINT, CTRUNE:
// single entry type switch return Mpcmpfixfix(n1.Val.U.Xval, n2.Val.U.Xval)
nvar.Ntype = typenod(ll.N.Type) case CTSTR:
} else { return cmpslit(n1, n2)
// multiple entry type switch or default
nvar.Ntype = typenod(n.Type)
} }
typecheck(&nvar, Erv|Easgn) return 0
ncase.Nname = nvar }
}
type caseClauseByType []*caseClause
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
// sort by hash code
case c1.hash != c2.hash:
return c1.hash < c2.hash
} }
typechecklist(ncase.Nbody, Etop) // sort by ordinal
return c1.ordinal < c2.ordinal
}
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)
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