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
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
......@@ -265,67 +331,54 @@ func casebody(sw *Node, typeswvar *Node) {
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)
......@@ -353,326 +406,103 @@ func casebody(sw *Node, typeswvar *Node) {
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
}
......@@ -688,26 +518,25 @@ func typeswitch(sw *Node) {
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)
......@@ -716,98 +545,81 @@ func typeswitch(sw *Node) {
}
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)
......@@ -816,161 +628,192 @@ func typeswitch(sw *Node) {
}
}
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")
}
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