| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package gc |
| |
| import ( |
| "cmd/compile/internal/types" |
| "fmt" |
| "sort" |
| ) |
| |
| const ( |
| // expression switch |
| switchKindExpr = iota // switch a {...} or switch 5 {...} |
| switchKindTrue // switch true {...} or switch {...} |
| switchKindFalse // switch false {...} |
| ) |
| |
| const ( |
| binarySearchMin = 4 // minimum number of cases for binary search |
| integerRangeMin = 2 // minimum size of integer ranges |
| ) |
| |
| // 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*) |
| } |
| |
| // 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 |
| } |
| |
| // 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 |
| // isconst indicates whether this case clause is a constant, |
| // for the purposes of the switch code generation. |
| // For expression switches, that's generally literals (case 5:, not case x:). |
| // For type switches, that's concrete types (case time.Time:), not interfaces (case io.Reader:). |
| isconst bool |
| } |
| |
| // caseClauses are all the case clauses in a switch statement. |
| type caseClauses struct { |
| list []caseClause // general cases |
| defjmp *Node // OGOTO for default case or OBREAK if no default case present |
| niljmp *Node // OGOTO for nil type case in a type switch |
| } |
| |
| // typecheckswitch typechecks a switch statement. |
| func typecheckswitch(n *Node) { |
| typecheckslice(n.Ninit.Slice(), ctxStmt) |
| |
| var nilonly string |
| var top int |
| var t *types.Type |
| |
| if n.Left != nil && n.Left.Op == OTYPESW { |
| // type switch |
| top = Etype |
| n.Left.Right = typecheck(n.Left.Right, ctxExpr) |
| t = n.Left.Right.Type |
| if t != nil && !t.IsInterface() { |
| yyerrorl(n.Pos, "cannot type switch on non-interface value %L", n.Left.Right) |
| } |
| if v := n.Left.Left; v != nil && !v.isBlank() && n.List.Len() == 0 { |
| // We don't actually declare the type switch's guarded |
| // declaration itself. So if there are no cases, we |
| // won't notice that it went unused. |
| yyerrorl(v.Pos, "%v declared and not used", v.Sym) |
| } |
| } else { |
| // expression switch |
| top = ctxExpr |
| if n.Left != nil { |
| n.Left = typecheck(n.Left, ctxExpr) |
| n.Left = defaultlit(n.Left, nil) |
| t = n.Left.Type |
| } else { |
| t = types.Types[TBOOL] |
| } |
| if t != nil { |
| switch { |
| case !okforeq[t.Etype]: |
| yyerrorl(n.Pos, "cannot switch on %L", n.Left) |
| case t.IsSlice(): |
| nilonly = "slice" |
| case t.IsArray() && !IsComparable(t): |
| yyerrorl(n.Pos, "cannot switch on %L", n.Left) |
| case t.IsStruct(): |
| if f := IncomparableField(t); f != nil { |
| yyerrorl(n.Pos, "cannot switch on %L (struct containing %v cannot be compared)", n.Left, f.Type) |
| } |
| case t.Etype == TFUNC: |
| nilonly = "func" |
| case t.IsMap(): |
| nilonly = "map" |
| } |
| } |
| } |
| |
| n.Type = t |
| |
| var def, niltype *Node |
| for _, ncase := range n.List.Slice() { |
| if ncase.List.Len() == 0 { |
| // default |
| if def != nil { |
| setlineno(ncase) |
| yyerrorl(ncase.Pos, "multiple defaults in switch (first at %v)", def.Line()) |
| } else { |
| def = ncase |
| } |
| } else { |
| ls := ncase.List.Slice() |
| for i1, n1 := range ls { |
| setlineno(n1) |
| ls[i1] = typecheck(ls[i1], ctxExpr|Etype) |
| n1 = ls[i1] |
| if n1.Type == nil || t == nil { |
| continue |
| } |
| |
| setlineno(ncase) |
| switch top { |
| // expression switch |
| case ctxExpr: |
| ls[i1] = defaultlit(ls[i1], t) |
| n1 = ls[i1] |
| switch { |
| case n1.Op == OTYPE: |
| yyerrorl(ncase.Pos, "type %v is not an expression", n1.Type) |
| case n1.Type != nil && assignop(n1.Type, t, nil) == 0 && assignop(t, n1.Type, nil) == 0: |
| if n.Left != nil { |
| yyerrorl(ncase.Pos, "invalid case %v in switch on %v (mismatched types %v and %v)", n1, n.Left, n1.Type, t) |
| } else { |
| yyerrorl(ncase.Pos, "invalid case %v in switch (mismatched types %v and bool)", n1, n1.Type) |
| } |
| case nilonly != "" && !n1.isNil(): |
| yyerrorl(ncase.Pos, "invalid case %v in switch (can only compare %s %v to nil)", n1, nilonly, n.Left) |
| case t.IsInterface() && !n1.Type.IsInterface() && !IsComparable(n1.Type): |
| yyerrorl(ncase.Pos, "invalid case %L in switch (incomparable type)", n1) |
| } |
| |
| // type switch |
| case Etype: |
| var missing, have *types.Field |
| var ptr int |
| switch { |
| case n1.Op == OLITERAL && n1.Type.IsKind(TNIL): |
| // case nil: |
| if niltype != nil { |
| yyerrorl(ncase.Pos, "multiple nil cases in type switch (first at %v)", niltype.Line()) |
| } else { |
| niltype = ncase |
| } |
| case n1.Op != OTYPE && n1.Type != nil: // should this be ||? |
| yyerrorl(ncase.Pos, "%L is not a type", n1) |
| // reset to original type |
| n1 = n.Left.Right |
| ls[i1] = n1 |
| case !n1.Type.IsInterface() && t.IsInterface() && !implements(n1.Type, t, &missing, &have, &ptr): |
| if have != nil && !missing.Broke() && !have.Broke() { |
| yyerrorl(ncase.Pos, "impossible type switch case: %L cannot have dynamic type %v"+ |
| " (wrong type for %v method)\n\thave %v%S\n\twant %v%S", n.Left.Right, n1.Type, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type) |
| } else if !missing.Broke() { |
| if ptr != 0 { |
| yyerrorl(ncase.Pos, "impossible type switch case: %L cannot have dynamic type %v"+ |
| " (%v method has pointer receiver)", n.Left.Right, n1.Type, missing.Sym) |
| } else { |
| yyerrorl(ncase.Pos, "impossible type switch case: %L cannot have dynamic type %v"+ |
| " (missing %v method)", n.Left.Right, n1.Type, missing.Sym) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if top == Etype { |
| ll := ncase.List |
| if ncase.Rlist.Len() != 0 { |
| nvar := ncase.Rlist.First() |
| if ll.Len() == 1 && (ll.First().Type == nil || !ll.First().Type.IsKind(TNIL)) { |
| // single entry type switch |
| nvar.Type = ll.First().Type |
| } else { |
| // multiple entry type switch or default |
| nvar.Type = n.Type |
| } |
| |
| if nvar.Type == nil || nvar.Type.IsUntyped() { |
| // if the value we're switching on has no type or is untyped, |
| // we've already printed an error and don't need to continue |
| // typechecking the body |
| continue |
| } |
| |
| nvar = typecheck(nvar, ctxExpr|ctxAssign) |
| ncase.Rlist.SetFirst(nvar) |
| } |
| } |
| |
| typecheckslice(ncase.Nbody.Slice(), ctxStmt) |
| } |
| switch top { |
| // expression switch |
| case ctxExpr: |
| checkDupExprCases(n.Left, n.List.Slice()) |
| } |
| } |
| |
| // walkswitch walks a switch statement. |
| func walkswitch(sw *Node) { |
| // convert switch {...} to switch true {...} |
| if sw.Left == nil { |
| sw.Left = nodbool(true) |
| sw.Left = typecheck(sw.Left, ctxExpr) |
| sw.Left = defaultlit(sw.Left, nil) |
| } |
| |
| if sw.Left.Op == OTYPESW { |
| var s typeSwitch |
| s.walk(sw) |
| } else { |
| var s exprSwitch |
| s.walk(sw) |
| } |
| } |
| |
| // 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) { |
| // Guard against double walk, see #25776. |
| if sw.List.Len() == 0 && sw.Nbody.Len() > 0 { |
| return // Was fatal, but eliminating every possible source of double-walking is hard |
| } |
| |
| casebody(sw, nil) |
| |
| cond := sw.Left |
| sw.Left = nil |
| |
| s.kind = switchKindExpr |
| if Isconst(cond, CTBOOL) { |
| s.kind = switchKindTrue |
| if !cond.Val().U.(bool) { |
| s.kind = switchKindFalse |
| } |
| } |
| |
| // Given "switch string(byteslice)", |
| // with all cases being constants (or the default case), |
| // use a zero-cost alias of the byte slice. |
| // In theory, we could be more aggressive, |
| // allowing any side-effect-free expressions in cases, |
| // but it's a bit tricky because some of that information |
| // is unavailable due to the introduction of temporaries during order. |
| // Restricting to constants is simple and probably powerful enough. |
| // Do this before calling walkexpr on cond, |
| // because walkexpr will lower the string |
| // conversion into a runtime call. |
| // See issue 24937 for more discussion. |
| if cond.Op == OBYTES2STR { |
| ok := true |
| for _, cas := range sw.List.Slice() { |
| if cas.Op != OCASE { |
| Fatalf("switch string(byteslice) bad op: %v", cas.Op) |
| } |
| if cas.Left != nil && !Isconst(cas.Left, CTSTR) { |
| ok = false |
| break |
| } |
| } |
| if ok { |
| cond.Op = OBYTES2STRTMP |
| } |
| } |
| |
| cond = walkexpr(cond, &sw.Ninit) |
| t := sw.Type |
| if t == nil { |
| return |
| } |
| |
| // convert the switch into OIF statements |
| var cas []*Node |
| if s.kind == switchKindTrue || s.kind == switchKindFalse { |
| s.exprname = nodbool(s.kind == switchKindTrue) |
| } else if consttype(cond) > 0 { |
| // leave constants to enable dead code elimination (issue 9608) |
| s.exprname = cond |
| } else { |
| s.exprname = temp(cond.Type) |
| cas = []*Node{nod(OAS, s.exprname, cond)} // This gets walk()ed again in walkstmtlist just before end of this function. See #29562. |
| typecheckslice(cas, ctxStmt) |
| } |
| |
| // Enumerate the cases and prepare the default case. |
| clauses := s.genCaseClauses(sw.List.Slice()) |
| sw.List.Set(nil) |
| cc := clauses.list |
| |
| // handle the cases in order |
| for len(cc) > 0 { |
| run := 1 |
| if okforcmp[t.Etype] && cc[0].isconst { |
| // do binary search on runs of constants |
| for ; run < len(cc) && cc[run].isconst; run++ { |
| } |
| // sort and compile constants |
| sort.Sort(caseClauseByConstVal(cc[:run])) |
| } |
| |
| a := s.walkCases(cc[:run]) |
| cas = append(cas, a) |
| cc = cc[run:] |
| } |
| |
| // handle default case |
| if nerrors == 0 { |
| cas = append(cas, clauses.defjmp) |
| sw.Nbody.Prepend(cas...) |
| walkstmtlist(sw.Nbody.Slice()) |
| } |
| } |
| |
| // walkCases generates an AST implementing the cases in cc. |
| func (s *exprSwitch) walkCases(cc []caseClause) *Node { |
| if len(cc) < binarySearchMin { |
| // linear search |
| var cas []*Node |
| for _, c := range cc { |
| n := c.node |
| lno := setlineno(n) |
| |
| a := nod(OIF, nil, nil) |
| if rng := n.List.Slice(); rng != nil { |
| // Integer range. |
| // exprname is a temp or a constant, |
| // so it is safe to evaluate twice. |
| // In most cases, this conjunction will be |
| // rewritten by walkinrange into a single comparison. |
| low := nod(OGE, s.exprname, rng[0]) |
| high := nod(OLE, s.exprname, rng[1]) |
| a.Left = nod(OANDAND, low, high) |
| } else 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.Left = nod(OEQ, s.exprname, n.Left) // if name == val |
| } else if s.kind == switchKindTrue { |
| a.Left = n.Left // if val |
| } else { |
| // s.kind == switchKindFalse |
| a.Left = nod(ONOT, n.Left, nil) // if !val |
| } |
| a.Left = typecheck(a.Left, ctxExpr) |
| a.Left = defaultlit(a.Left, nil) |
| a.Nbody.Set1(n.Right) // goto l |
| |
| cas = append(cas, a) |
| lineno = lno |
| } |
| return liststmt(cas) |
| } |
| |
| // find the middle and recur |
| half := len(cc) / 2 |
| a := nod(OIF, nil, nil) |
| n := cc[half-1].node |
| var mid *Node |
| if rng := n.List.Slice(); rng != nil { |
| mid = rng[1] // high end of range |
| } else { |
| mid = n.Left |
| } |
| le := nod(OLE, s.exprname, mid) |
| if Isconst(mid, CTSTR) { |
| // Search by length and then by value; see caseClauseByConstVal. |
| lenlt := nod(OLT, nod(OLEN, s.exprname, nil), nod(OLEN, mid, nil)) |
| leneq := nod(OEQ, nod(OLEN, s.exprname, nil), nod(OLEN, mid, nil)) |
| a.Left = nod(OOROR, lenlt, nod(OANDAND, leneq, le)) |
| } else { |
| a.Left = le |
| } |
| a.Left = typecheck(a.Left, ctxExpr) |
| a.Left = defaultlit(a.Left, nil) |
| a.Nbody.Set1(s.walkCases(cc[:half])) |
| a.Rlist.Set1(s.walkCases(cc[half:])) |
| return a |
| } |
| |
| // 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.Len() == 0 { |
| return |
| } |
| |
| lno := setlineno(sw) |
| |
| var cas []*Node // cases |
| var stat []*Node // statements |
| var def *Node // defaults |
| br := nod(OBREAK, nil, nil) |
| |
| for _, n := range sw.List.Slice() { |
| setlineno(n) |
| if n.Op != OXCASE { |
| Fatalf("casebody %v", n.Op) |
| } |
| n.Op = OCASE |
| needvar := n.List.Len() != 1 || n.List.First().Op == OLITERAL |
| |
| lbl := autolabel(".s") |
| jmp := nodSym(OGOTO, nil, lbl) |
| switch n.List.Len() { |
| case 0: |
| // default |
| if def != nil { |
| yyerrorl(n.Pos, "more than one default case") |
| } |
| // reuse original default case |
| n.Right = jmp |
| def = n |
| case 1: |
| // one case -- reuse OCASE node |
| n.Left = n.List.First() |
| n.Right = jmp |
| n.List.Set(nil) |
| cas = append(cas, n) |
| default: |
| // Expand multi-valued cases and detect ranges of integer cases. |
| if typeswvar != nil || sw.Left.Type.IsInterface() || !n.List.First().Type.IsInteger() || n.List.Len() < integerRangeMin { |
| // Can't use integer ranges. Expand each case into a separate node. |
| for _, n1 := range n.List.Slice() { |
| cas = append(cas, nod(OCASE, n1, jmp)) |
| } |
| break |
| } |
| // Find integer ranges within runs of constants. |
| s := n.List.Slice() |
| j := 0 |
| for j < len(s) { |
| // Find a run of constants. |
| var run int |
| for run = j; run < len(s) && Isconst(s[run], CTINT); run++ { |
| } |
| if run-j >= integerRangeMin { |
| // Search for integer ranges in s[j:run]. |
| // Typechecking is done, so all values are already in an appropriate range. |
| search := s[j:run] |
| sort.Sort(constIntNodesByVal(search)) |
| for beg, end := 0, 1; end <= len(search); end++ { |
| if end < len(search) && search[end].Int64() == search[end-1].Int64()+1 { |
| continue |
| } |
| if end-beg >= integerRangeMin { |
| // Record range in List. |
| c := nod(OCASE, nil, jmp) |
| c.List.Set2(search[beg], search[end-1]) |
| cas = append(cas, c) |
| } else { |
| // Not large enough for range; record separately. |
| for _, n := range search[beg:end] { |
| cas = append(cas, nod(OCASE, n, jmp)) |
| } |
| } |
| beg = end |
| } |
| j = run |
| } |
| // Advance to next constant, adding individual non-constant |
| // or as-yet-unhandled constant cases as we go. |
| for ; j < len(s) && (j < run || !Isconst(s[j], CTINT)); j++ { |
| cas = append(cas, nod(OCASE, s[j], jmp)) |
| } |
| } |
| } |
| |
| stat = append(stat, nodSym(OLABEL, nil, lbl)) |
| if typeswvar != nil && needvar && n.Rlist.Len() != 0 { |
| l := []*Node{ |
| nod(ODCL, n.Rlist.First(), nil), |
| nod(OAS, n.Rlist.First(), typeswvar), |
| } |
| typecheckslice(l, ctxStmt) |
| stat = append(stat, l...) |
| } |
| stat = append(stat, n.Nbody.Slice()...) |
| |
| // Search backwards for the index of the fallthrough |
| // statement. Do not assume it'll be in the last |
| // position, since in some cases (e.g. when the statement |
| // list contains autotmp_ variables), one or more OVARKILL |
| // nodes will be at the end of the list. |
| fallIndex := len(stat) - 1 |
| for stat[fallIndex].Op == OVARKILL { |
| fallIndex-- |
| } |
| last := stat[fallIndex] |
| if last.Op != OFALL { |
| stat = append(stat, br) |
| } |
| } |
| |
| stat = append(stat, br) |
| if def != nil { |
| cas = append(cas, def) |
| } |
| |
| sw.List.Set(cas) |
| sw.Nbody.Set(stat) |
| lineno = lno |
| } |
| |
| // genCaseClauses generates the caseClauses value for clauses. |
| func (s *exprSwitch) genCaseClauses(clauses []*Node) caseClauses { |
| var cc caseClauses |
| for _, n := range clauses { |
| if n.Left == nil && n.List.Len() == 0 { |
| // default case |
| if cc.defjmp != nil { |
| Fatalf("duplicate default case not detected during typechecking") |
| } |
| cc.defjmp = n.Right |
| continue |
| } |
| c := caseClause{node: n, ordinal: len(cc.list)} |
| if n.List.Len() > 0 { |
| c.isconst = true |
| } |
| switch consttype(n.Left) { |
| case CTFLT, CTINT, CTRUNE, CTSTR: |
| c.isconst = true |
| } |
| cc.list = append(cc.list, c) |
| } |
| |
| if cc.defjmp == nil { |
| cc.defjmp = nod(OBREAK, nil, nil) |
| } |
| return cc |
| } |
| |
| // genCaseClauses generates the caseClauses value for clauses. |
| func (s *typeSwitch) genCaseClauses(clauses []*Node) caseClauses { |
| var cc caseClauses |
| for _, n := range clauses { |
| switch { |
| case n.Left == nil: |
| // default case |
| if cc.defjmp != nil { |
| Fatalf("duplicate default case not detected during typechecking") |
| } |
| cc.defjmp = n.Right |
| continue |
| case n.Left.Op == OLITERAL: |
| // nil case in type switch |
| if cc.niljmp != nil { |
| Fatalf("duplicate nil case not detected during typechecking") |
| } |
| cc.niljmp = n.Right |
| continue |
| } |
| |
| // general case |
| c := caseClause{ |
| node: n, |
| ordinal: len(cc.list), |
| isconst: !n.Left.Type.IsInterface(), |
| hash: typehash(n.Left.Type), |
| } |
| cc.list = append(cc.list, c) |
| } |
| |
| if cc.defjmp == nil { |
| cc.defjmp = nod(OBREAK, nil, nil) |
| } |
| |
| // diagnose duplicate cases |
| s.checkDupCases(cc.list) |
| return cc |
| } |
| |
| func (s *typeSwitch) checkDupCases(cc []caseClause) { |
| if len(cc) < 2 { |
| return |
| } |
| // We store seen types in a map keyed by type hash. |
| // It is possible, but very unlikely, for multiple distinct types to have the same hash. |
| seen := make(map[uint32][]*Node) |
| // To avoid many small allocations of length 1 slices, |
| // also set up a single large slice to slice into. |
| nn := make([]*Node, 0, len(cc)) |
| Outer: |
| for _, c := range cc { |
| prev, ok := seen[c.hash] |
| if !ok { |
| // First entry for this hash. |
| nn = append(nn, c.node) |
| seen[c.hash] = nn[len(nn)-1 : len(nn) : len(nn)] |
| continue |
| } |
| for _, n := range prev { |
| if types.Identical(n.Left.Type, c.node.Left.Type) { |
| yyerrorl(c.node.Pos, "duplicate case %v in type switch\n\tprevious case at %v", c.node.Left.Type, n.Line()) |
| // avoid double-reporting errors |
| continue Outer |
| } |
| } |
| seen[c.hash] = append(seen[c.hash], c.node) |
| } |
| } |
| |
| func checkDupExprCases(exprname *Node, clauses []*Node) { |
| // boolean (naked) switch, nothing to do. |
| if exprname == nil { |
| return |
| } |
| |
| var cs constSet |
| for _, ncase := range clauses { |
| for _, n := range ncase.List.Slice() { |
| // Don't check for duplicate bools. Although the spec allows it, |
| // (1) the compiler hasn't checked it in the past, so compatibility mandates it, and |
| // (2) it would disallow useful things like |
| // case GOARCH == "arm" && GOARM == "5": |
| // case GOARCH == "arm": |
| // which would both evaluate to false for non-ARM compiles. |
| if n.Type.IsBoolean() { |
| continue |
| } |
| |
| if prev := cs.add(n); prev != nil { |
| yyerrorl(ncase.Pos, "duplicate case %s in switch\n\tprevious case at %v", |
| nodeAndVal(n), prev.Line()) |
| } |
| } |
| } |
| } |
| |
| func nodeAndVal(n *Node) string { |
| show := n.String() |
| val := n.Val().Interface() |
| if s := fmt.Sprintf("%#v", val); show != s { |
| show += " (value " + s + ")" |
| } |
| return show |
| } |
| |
| // 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) { |
| cond := sw.Left |
| sw.Left = nil |
| |
| if cond == nil { |
| sw.List.Set(nil) |
| return |
| } |
| if cond.Right == nil { |
| yyerrorl(sw.Pos, "type switch must have an assignment") |
| return |
| } |
| |
| cond.Right = walkexpr(cond.Right, &sw.Ninit) |
| if !cond.Right.Type.IsInterface() { |
| yyerrorl(sw.Pos, "type switch must be on an interface") |
| return |
| } |
| |
| var cas []*Node |
| |
| // predeclare temporary variables and the boolean var |
| s.facename = temp(cond.Right.Type) |
| |
| a := nod(OAS, s.facename, cond.Right) |
| a = typecheck(a, ctxStmt) |
| cas = append(cas, a) |
| |
| s.okname = temp(types.Types[TBOOL]) |
| s.okname = typecheck(s.okname, ctxExpr) |
| |
| s.hashname = temp(types.Types[TUINT32]) |
| s.hashname = typecheck(s.hashname, ctxExpr) |
| |
| // set up labels and jumps |
| casebody(sw, s.facename) |
| |
| clauses := s.genCaseClauses(sw.List.Slice()) |
| sw.List.Set(nil) |
| def := clauses.defjmp |
| |
| // For empty interfaces, do: |
| // if e._type == nil { |
| // do nil case if it exists, otherwise default |
| // } |
| // h := e._type.hash |
| // Use a similar strategy for non-empty interfaces. |
| |
| // Get interface descriptor word. |
| // For empty interfaces this will be the type. |
| // For non-empty interfaces this will be the itab. |
| itab := nod(OITAB, s.facename, nil) |
| |
| // Check for nil first. |
| i := nod(OIF, nil, nil) |
| i.Left = nod(OEQ, itab, nodnil()) |
| if clauses.niljmp != nil { |
| // Do explicit nil case right here. |
| i.Nbody.Set1(clauses.niljmp) |
| } else { |
| // Jump to default case. |
| lbl := autolabel(".s") |
| i.Nbody.Set1(nodSym(OGOTO, nil, lbl)) |
| // Wrap default case with label. |
| blk := nod(OBLOCK, nil, nil) |
| blk.List.Set2(nodSym(OLABEL, nil, lbl), def) |
| def = blk |
| } |
| i.Left = typecheck(i.Left, ctxExpr) |
| i.Left = defaultlit(i.Left, nil) |
| cas = append(cas, i) |
| |
| // Load hash from type or itab. |
| h := nodSym(ODOTPTR, itab, nil) |
| h.Type = types.Types[TUINT32] |
| h.SetTypecheck(1) |
| if cond.Right.Type.IsEmptyInterface() { |
| h.Xoffset = int64(2 * Widthptr) // offset of hash in runtime._type |
| } else { |
| h.Xoffset = int64(2 * Widthptr) // offset of hash in runtime.itab |
| } |
| h.SetBounded(true) // guaranteed not to fault |
| a = nod(OAS, s.hashname, h) |
| a = typecheck(a, ctxStmt) |
| cas = append(cas, a) |
| |
| cc := clauses.list |
| |
| // insert type equality check into each case block |
| for _, c := range cc { |
| c.node.Right = s.typeone(c.node) |
| } |
| |
| // generate list of if statements, binary search for constant sequences |
| for len(cc) > 0 { |
| if !cc[0].isconst { |
| n := cc[0].node |
| cas = append(cas, n.Right) |
| cc = cc[1:] |
| continue |
| } |
| |
| // identify run of constants |
| var run int |
| for run = 1; run < len(cc) && cc[run].isconst; run++ { |
| } |
| |
| // sort by hash |
| sort.Sort(caseClauseByType(cc[:run])) |
| |
| // for debugging: linear search |
| if false { |
| for i := 0; i < run; i++ { |
| n := cc[i].node |
| cas = append(cas, n.Right) |
| } |
| continue |
| } |
| |
| // combine adjacent cases with the same hash |
| var batch []caseClause |
| for i, j := 0, 0; i < run; i = j { |
| hash := []*Node{cc[i].node.Right} |
| for j = i + 1; j < run && cc[i].hash == cc[j].hash; j++ { |
| hash = append(hash, cc[j].node.Right) |
| } |
| cc[i].node.Right = liststmt(hash) |
| batch = append(batch, cc[i]) |
| } |
| |
| // binary search among cases to narrow by hash |
| cas = append(cas, s.walkCases(batch)) |
| cc = cc[run:] |
| } |
| |
| // handle default case |
| if nerrors == 0 { |
| cas = append(cas, def) |
| sw.Nbody.Prepend(cas...) |
| sw.List.Set(nil) |
| walkstmtlist(sw.Nbody.Slice()) |
| } |
| } |
| |
| // typeone generates an AST that jumps to the |
| // case body if the variable is of type t. |
| func (s *typeSwitch) typeone(t *Node) *Node { |
| var name *Node |
| var init Nodes |
| if t.Rlist.Len() == 0 { |
| name = nblank |
| nblank = typecheck(nblank, ctxExpr|ctxAssign) |
| } else { |
| name = t.Rlist.First() |
| init.Append(nod(ODCL, name, nil)) |
| a := nod(OAS, name, nil) |
| a = typecheck(a, ctxStmt) |
| init.Append(a) |
| } |
| |
| a := nod(OAS2, nil, nil) |
| a.List.Set2(name, s.okname) // name, ok = |
| b := nod(ODOTTYPE, s.facename, nil) |
| b.Type = t.Left.Type // interface.(type) |
| a.Rlist.Set1(b) |
| a = typecheck(a, ctxStmt) |
| a = walkexpr(a, &init) |
| init.Append(a) |
| |
| c := nod(OIF, nil, nil) |
| c.Left = s.okname |
| c.Nbody.Set1(t.Right) // if ok { goto l } |
| |
| init.Append(c) |
| return init.asblock() |
| } |
| |
| // walkCases generates an AST implementing the cases in cc. |
| func (s *typeSwitch) walkCases(cc []caseClause) *Node { |
| if len(cc) < binarySearchMin { |
| var cas []*Node |
| for _, c := range cc { |
| n := c.node |
| if !c.isconst { |
| Fatalf("typeSwitch walkCases") |
| } |
| a := nod(OIF, nil, nil) |
| a.Left = nod(OEQ, s.hashname, nodintconst(int64(c.hash))) |
| a.Left = typecheck(a.Left, ctxExpr) |
| a.Left = defaultlit(a.Left, nil) |
| a.Nbody.Set1(n.Right) |
| cas = append(cas, a) |
| } |
| return liststmt(cas) |
| } |
| |
| // find the middle and recur |
| half := len(cc) / 2 |
| a := nod(OIF, nil, nil) |
| a.Left = nod(OLE, s.hashname, nodintconst(int64(cc[half-1].hash))) |
| a.Left = typecheck(a.Left, ctxExpr) |
| a.Left = defaultlit(a.Left, nil) |
| a.Nbody.Set1(s.walkCases(cc[:half])) |
| a.Rlist.Set1(s.walkCases(cc[half:])) |
| return a |
| } |
| |
| // caseClauseByConstVal sorts clauses by constant value to enable binary search. |
| type caseClauseByConstVal []caseClause |
| |
| func (x caseClauseByConstVal) Len() int { return len(x) } |
| func (x caseClauseByConstVal) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |
| func (x caseClauseByConstVal) Less(i, j int) bool { |
| // n1 and n2 might be individual constants or integer ranges. |
| // We have checked for duplicates already, |
| // so ranges can be safely represented by any value in the range. |
| n1 := x[i].node |
| var v1 interface{} |
| if s := n1.List.Slice(); s != nil { |
| v1 = s[0].Val().U |
| } else { |
| v1 = n1.Left.Val().U |
| } |
| |
| n2 := x[j].node |
| var v2 interface{} |
| if s := n2.List.Slice(); s != nil { |
| v2 = s[0].Val().U |
| } else { |
| v2 = n2.Left.Val().U |
| } |
| |
| switch v1 := v1.(type) { |
| case *Mpflt: |
| return v1.Cmp(v2.(*Mpflt)) < 0 |
| case *Mpint: |
| return v1.Cmp(v2.(*Mpint)) < 0 |
| case string: |
| // Sort strings by length and then by value. |
| // It is much cheaper to compare lengths than values, |
| // and all we need here is consistency. |
| // We respect this sorting in exprSwitch.walkCases. |
| a := v1 |
| b := v2.(string) |
| if len(a) != len(b) { |
| return len(a) < len(b) |
| } |
| return a < b |
| } |
| |
| Fatalf("caseClauseByConstVal passed bad clauses %v < %v", x[i].node.Left, x[j].node.Left) |
| return false |
| } |
| |
| 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] |
| // sort by hash code, then ordinal (for the rare case of hash collisions) |
| if c1.hash != c2.hash { |
| return c1.hash < c2.hash |
| } |
| return c1.ordinal < c2.ordinal |
| } |
| |
| type constIntNodesByVal []*Node |
| |
| func (x constIntNodesByVal) Len() int { return len(x) } |
| func (x constIntNodesByVal) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |
| func (x constIntNodesByVal) Less(i, j int) bool { |
| return x[i].Val().U.(*Mpint).Cmp(x[j].Val().U.(*Mpint)) < 0 |
| } |