summaryrefslogtreecommitdiffstats
path: root/src/cmd/compile/internal/gc/swt.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/compile/internal/gc/swt.go')
-rw-r--r--src/cmd/compile/internal/gc/swt.go756
1 files changed, 756 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/gc/swt.go b/src/cmd/compile/internal/gc/swt.go
new file mode 100644
index 0000000..8d9fbe3
--- /dev/null
+++ b/src/cmd/compile/internal/gc/swt.go
@@ -0,0 +1,756 @@
+// 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"
+ "cmd/internal/src"
+ "sort"
+)
+
+// typecheckswitch typechecks a switch statement.
+func typecheckswitch(n *Node) {
+ typecheckslice(n.Ninit.Slice(), ctxStmt)
+ if n.Left != nil && n.Left.Op == OTYPESW {
+ typecheckTypeSwitch(n)
+ } else {
+ typecheckExprSwitch(n)
+ }
+}
+
+func typecheckTypeSwitch(n *Node) {
+ 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)
+ t = nil
+ }
+
+ // 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.
+ if v := n.Left.Left; v != nil && !v.isBlank() && n.List.Len() == 0 {
+ yyerrorl(v.Pos, "%v declared but not used", v.Sym)
+ }
+
+ var defCase, nilCase *Node
+ var ts typeSet
+ for _, ncase := range n.List.Slice() {
+ ls := ncase.List.Slice()
+ if len(ls) == 0 { // default:
+ if defCase != nil {
+ yyerrorl(ncase.Pos, "multiple defaults in switch (first at %v)", defCase.Line())
+ } else {
+ defCase = ncase
+ }
+ }
+
+ for i := range ls {
+ ls[i] = typecheck(ls[i], ctxExpr|ctxType)
+ n1 := ls[i]
+ if t == nil || n1.Type == nil {
+ continue
+ }
+
+ var missing, have *types.Field
+ var ptr int
+ switch {
+ case n1.isNil(): // case nil:
+ if nilCase != nil {
+ yyerrorl(ncase.Pos, "multiple nil cases in type switch (first at %v)", nilCase.Line())
+ } else {
+ nilCase = ncase
+ }
+ case n1.Op != OTYPE:
+ yyerrorl(ncase.Pos, "%L is not a type", n1)
+ case !n1.Type.IsInterface() && !implements(n1.Type, t, &missing, &have, &ptr) && !missing.Broke():
+ if have != nil && !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 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 n1.Op == OTYPE {
+ ts.add(ncase.Pos, n1.Type)
+ }
+ }
+
+ if ncase.Rlist.Len() != 0 {
+ // Assign the clause variable's type.
+ vt := t
+ if len(ls) == 1 {
+ if ls[0].Op == OTYPE {
+ vt = ls[0].Type
+ } else if ls[0].Op != OLITERAL { // TODO(mdempsky): Should be !ls[0].isNil()
+ // Invalid single-type case;
+ // mark variable as broken.
+ vt = nil
+ }
+ }
+
+ // TODO(mdempsky): It should be possible to
+ // still typecheck the case body.
+ if vt == nil {
+ continue
+ }
+
+ nvar := ncase.Rlist.First()
+ nvar.Type = vt
+ nvar = typecheck(nvar, ctxExpr|ctxAssign)
+ ncase.Rlist.SetFirst(nvar)
+ }
+
+ typecheckslice(ncase.Nbody.Slice(), ctxStmt)
+ }
+}
+
+type typeSet struct {
+ m map[string][]typeSetEntry
+}
+
+type typeSetEntry struct {
+ pos src.XPos
+ typ *types.Type
+}
+
+func (s *typeSet) add(pos src.XPos, typ *types.Type) {
+ if s.m == nil {
+ s.m = make(map[string][]typeSetEntry)
+ }
+
+ // LongString does not uniquely identify types, so we need to
+ // disambiguate collisions with types.Identical.
+ // TODO(mdempsky): Add a method that *is* unique.
+ ls := typ.LongString()
+ prevs := s.m[ls]
+ for _, prev := range prevs {
+ if types.Identical(typ, prev.typ) {
+ yyerrorl(pos, "duplicate case %v in type switch\n\tprevious case at %s", typ, linestr(prev.pos))
+ return
+ }
+ }
+ s.m[ls] = append(prevs, typeSetEntry{pos, typ})
+}
+
+func typecheckExprSwitch(n *Node) {
+ t := types.Types[TBOOL]
+ if n.Left != nil {
+ n.Left = typecheck(n.Left, ctxExpr)
+ n.Left = defaultlit(n.Left, nil)
+ t = n.Left.Type
+ }
+
+ var nilonly string
+ if t != nil {
+ switch {
+ case t.IsMap():
+ nilonly = "map"
+ case t.Etype == TFUNC:
+ nilonly = "func"
+ case t.IsSlice():
+ nilonly = "slice"
+
+ case !IsComparable(t):
+ if t.IsStruct() {
+ yyerrorl(n.Pos, "cannot switch on %L (struct containing %v cannot be compared)", n.Left, IncomparableField(t).Type)
+ } else {
+ yyerrorl(n.Pos, "cannot switch on %L", n.Left)
+ }
+ t = nil
+ }
+ }
+
+ var defCase *Node
+ var cs constSet
+ for _, ncase := range n.List.Slice() {
+ ls := ncase.List.Slice()
+ if len(ls) == 0 { // default:
+ if defCase != nil {
+ yyerrorl(ncase.Pos, "multiple defaults in switch (first at %v)", defCase.Line())
+ } else {
+ defCase = ncase
+ }
+ }
+
+ for i := range ls {
+ setlineno(ncase)
+ ls[i] = typecheck(ls[i], ctxExpr)
+ ls[i] = defaultlit(ls[i], t)
+ n1 := ls[i]
+ if t == nil || n1.Type == nil {
+ continue
+ }
+
+ if nilonly != "" && !n1.isNil() {
+ yyerrorl(ncase.Pos, "invalid case %v in switch (can only compare %s %v to nil)", n1, nilonly, n.Left)
+ } else if t.IsInterface() && !n1.Type.IsInterface() && !IsComparable(n1.Type) {
+ yyerrorl(ncase.Pos, "invalid case %L in switch (incomparable type)", n1)
+ } else {
+ op1, _ := assignop(n1.Type, t)
+ op2, _ := assignop(t, n1.Type)
+ if op1 == OXXX && op2 == OXXX {
+ 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)
+ }
+ }
+ }
+
+ // 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 !n1.Type.IsBoolean() {
+ cs.add(ncase.Pos, n1, "case", "switch")
+ }
+ }
+
+ typecheckslice(ncase.Nbody.Slice(), ctxStmt)
+ }
+}
+
+// walkswitch walks a switch statement.
+func walkswitch(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
+ }
+
+ if sw.Left != nil && sw.Left.Op == OTYPESW {
+ walkTypeSwitch(sw)
+ } else {
+ walkExprSwitch(sw)
+ }
+}
+
+// walkExprSwitch generates an AST implementing sw. sw is an
+// expression switch.
+func walkExprSwitch(sw *Node) {
+ lno := setlineno(sw)
+
+ cond := sw.Left
+ sw.Left = nil
+
+ // convert switch {...} to switch true {...}
+ if cond == nil {
+ cond = nodbool(true)
+ cond = typecheck(cond, ctxExpr)
+ cond = defaultlit(cond, nil)
+ }
+
+ // Given "switch string(byteslice)",
+ // with all cases being side-effect free,
+ // use a zero-cost alias of the byte slice.
+ // 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 && allCaseExprsAreSideEffectFree(sw) {
+ cond.Op = OBYTES2STRTMP
+ }
+
+ cond = walkexpr(cond, &sw.Ninit)
+ if cond.Op != OLITERAL {
+ cond = copyexpr(cond, cond.Type, &sw.Nbody)
+ }
+
+ lineno = lno
+
+ s := exprSwitch{
+ exprname: cond,
+ }
+
+ var defaultGoto *Node
+ var body Nodes
+ for _, ncase := range sw.List.Slice() {
+ label := autolabel(".s")
+ jmp := npos(ncase.Pos, nodSym(OGOTO, nil, label))
+
+ // Process case dispatch.
+ if ncase.List.Len() == 0 {
+ if defaultGoto != nil {
+ Fatalf("duplicate default case not detected during typechecking")
+ }
+ defaultGoto = jmp
+ }
+
+ for _, n1 := range ncase.List.Slice() {
+ s.Add(ncase.Pos, n1, jmp)
+ }
+
+ // Process body.
+ body.Append(npos(ncase.Pos, nodSym(OLABEL, nil, label)))
+ body.Append(ncase.Nbody.Slice()...)
+ if fall, pos := hasFall(ncase.Nbody.Slice()); !fall {
+ br := nod(OBREAK, nil, nil)
+ br.Pos = pos
+ body.Append(br)
+ }
+ }
+ sw.List.Set(nil)
+
+ if defaultGoto == nil {
+ br := nod(OBREAK, nil, nil)
+ br.Pos = br.Pos.WithNotStmt()
+ defaultGoto = br
+ }
+
+ s.Emit(&sw.Nbody)
+ sw.Nbody.Append(defaultGoto)
+ sw.Nbody.AppendNodes(&body)
+ walkstmtlist(sw.Nbody.Slice())
+}
+
+// An exprSwitch walks an expression switch.
+type exprSwitch struct {
+ exprname *Node // value being switched on
+
+ done Nodes
+ clauses []exprClause
+}
+
+type exprClause struct {
+ pos src.XPos
+ lo, hi *Node
+ jmp *Node
+}
+
+func (s *exprSwitch) Add(pos src.XPos, expr, jmp *Node) {
+ c := exprClause{pos: pos, lo: expr, hi: expr, jmp: jmp}
+ if okforcmp[s.exprname.Type.Etype] && expr.Op == OLITERAL {
+ s.clauses = append(s.clauses, c)
+ return
+ }
+
+ s.flush()
+ s.clauses = append(s.clauses, c)
+ s.flush()
+}
+
+func (s *exprSwitch) Emit(out *Nodes) {
+ s.flush()
+ out.AppendNodes(&s.done)
+}
+
+func (s *exprSwitch) flush() {
+ cc := s.clauses
+ s.clauses = nil
+ if len(cc) == 0 {
+ return
+ }
+
+ // Caution: If len(cc) == 1, then cc[0] might not an OLITERAL.
+ // The code below is structured to implicitly handle this case
+ // (e.g., sort.Slice doesn't need to invoke the less function
+ // when there's only a single slice element).
+
+ if s.exprname.Type.IsString() && len(cc) >= 2 {
+ // 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 below.
+ sort.Slice(cc, func(i, j int) bool {
+ si := cc[i].lo.StringVal()
+ sj := cc[j].lo.StringVal()
+ if len(si) != len(sj) {
+ return len(si) < len(sj)
+ }
+ return si < sj
+ })
+
+ // runLen returns the string length associated with a
+ // particular run of exprClauses.
+ runLen := func(run []exprClause) int64 { return int64(len(run[0].lo.StringVal())) }
+
+ // Collapse runs of consecutive strings with the same length.
+ var runs [][]exprClause
+ start := 0
+ for i := 1; i < len(cc); i++ {
+ if runLen(cc[start:]) != runLen(cc[i:]) {
+ runs = append(runs, cc[start:i])
+ start = i
+ }
+ }
+ runs = append(runs, cc[start:])
+
+ // Perform two-level binary search.
+ nlen := nod(OLEN, s.exprname, nil)
+ binarySearch(len(runs), &s.done,
+ func(i int) *Node {
+ return nod(OLE, nlen, nodintconst(runLen(runs[i-1])))
+ },
+ func(i int, nif *Node) {
+ run := runs[i]
+ nif.Left = nod(OEQ, nlen, nodintconst(runLen(run)))
+ s.search(run, &nif.Nbody)
+ },
+ )
+ return
+ }
+
+ sort.Slice(cc, func(i, j int) bool {
+ return compareOp(cc[i].lo.Val(), OLT, cc[j].lo.Val())
+ })
+
+ // Merge consecutive integer cases.
+ if s.exprname.Type.IsInteger() {
+ merged := cc[:1]
+ for _, c := range cc[1:] {
+ last := &merged[len(merged)-1]
+ if last.jmp == c.jmp && last.hi.Int64Val()+1 == c.lo.Int64Val() {
+ last.hi = c.lo
+ } else {
+ merged = append(merged, c)
+ }
+ }
+ cc = merged
+ }
+
+ s.search(cc, &s.done)
+}
+
+func (s *exprSwitch) search(cc []exprClause, out *Nodes) {
+ binarySearch(len(cc), out,
+ func(i int) *Node {
+ return nod(OLE, s.exprname, cc[i-1].hi)
+ },
+ func(i int, nif *Node) {
+ c := &cc[i]
+ nif.Left = c.test(s.exprname)
+ nif.Nbody.Set1(c.jmp)
+ },
+ )
+}
+
+func (c *exprClause) test(exprname *Node) *Node {
+ // Integer range.
+ if c.hi != c.lo {
+ low := nodl(c.pos, OGE, exprname, c.lo)
+ high := nodl(c.pos, OLE, exprname, c.hi)
+ return nodl(c.pos, OANDAND, low, high)
+ }
+
+ // Optimize "switch true { ...}" and "switch false { ... }".
+ if Isconst(exprname, CTBOOL) && !c.lo.Type.IsInterface() {
+ if exprname.BoolVal() {
+ return c.lo
+ } else {
+ return nodl(c.pos, ONOT, c.lo, nil)
+ }
+ }
+
+ return nodl(c.pos, OEQ, exprname, c.lo)
+}
+
+func allCaseExprsAreSideEffectFree(sw *Node) bool {
+ // 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.
+
+ for _, ncase := range sw.List.Slice() {
+ if ncase.Op != OCASE {
+ Fatalf("switch string(byteslice) bad op: %v", ncase.Op)
+ }
+ for _, v := range ncase.List.Slice() {
+ if v.Op != OLITERAL {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// hasFall reports whether stmts ends with a "fallthrough" statement.
+func hasFall(stmts []*Node) (bool, src.XPos) {
+ // 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.
+
+ i := len(stmts) - 1
+ for i >= 0 && stmts[i].Op == OVARKILL {
+ i--
+ }
+ if i < 0 {
+ return false, src.NoXPos
+ }
+ return stmts[i].Op == OFALL, stmts[i].Pos
+}
+
+// walkTypeSwitch generates an AST that implements sw, where sw is a
+// type switch.
+func walkTypeSwitch(sw *Node) {
+ var s typeSwitch
+ s.facename = sw.Left.Right
+ sw.Left = nil
+
+ s.facename = walkexpr(s.facename, &sw.Ninit)
+ s.facename = copyexpr(s.facename, s.facename.Type, &sw.Nbody)
+ s.okname = temp(types.Types[TBOOL])
+
+ // 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)
+
+ // 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.
+ ifNil := nod(OIF, nil, nil)
+ ifNil.Left = nod(OEQ, itab, nodnil())
+ lineno = lineno.WithNotStmt() // disable statement marks after the first check.
+ ifNil.Left = typecheck(ifNil.Left, ctxExpr)
+ ifNil.Left = defaultlit(ifNil.Left, nil)
+ // ifNil.Nbody assigned at end.
+ sw.Nbody.Append(ifNil)
+
+ // Load hash from type or itab.
+ dotHash := nodSym(ODOTPTR, itab, nil)
+ dotHash.Type = types.Types[TUINT32]
+ dotHash.SetTypecheck(1)
+ if s.facename.Type.IsEmptyInterface() {
+ dotHash.Xoffset = int64(2 * Widthptr) // offset of hash in runtime._type
+ } else {
+ dotHash.Xoffset = int64(2 * Widthptr) // offset of hash in runtime.itab
+ }
+ dotHash.SetBounded(true) // guaranteed not to fault
+ s.hashname = copyexpr(dotHash, dotHash.Type, &sw.Nbody)
+
+ br := nod(OBREAK, nil, nil)
+ var defaultGoto, nilGoto *Node
+ var body Nodes
+ for _, ncase := range sw.List.Slice() {
+ var caseVar *Node
+ if ncase.Rlist.Len() != 0 {
+ caseVar = ncase.Rlist.First()
+ }
+
+ // For single-type cases with an interface type,
+ // we initialize the case variable as part of the type assertion.
+ // In other cases, we initialize it in the body.
+ var singleType *types.Type
+ if ncase.List.Len() == 1 && ncase.List.First().Op == OTYPE {
+ singleType = ncase.List.First().Type
+ }
+ caseVarInitialized := false
+
+ label := autolabel(".s")
+ jmp := npos(ncase.Pos, nodSym(OGOTO, nil, label))
+
+ if ncase.List.Len() == 0 { // default:
+ if defaultGoto != nil {
+ Fatalf("duplicate default case not detected during typechecking")
+ }
+ defaultGoto = jmp
+ }
+
+ for _, n1 := range ncase.List.Slice() {
+ if n1.isNil() { // case nil:
+ if nilGoto != nil {
+ Fatalf("duplicate nil case not detected during typechecking")
+ }
+ nilGoto = jmp
+ continue
+ }
+
+ if singleType != nil && singleType.IsInterface() {
+ s.Add(ncase.Pos, n1.Type, caseVar, jmp)
+ caseVarInitialized = true
+ } else {
+ s.Add(ncase.Pos, n1.Type, nil, jmp)
+ }
+ }
+
+ body.Append(npos(ncase.Pos, nodSym(OLABEL, nil, label)))
+ if caseVar != nil && !caseVarInitialized {
+ val := s.facename
+ if singleType != nil {
+ // We have a single concrete type. Extract the data.
+ if singleType.IsInterface() {
+ Fatalf("singleType interface should have been handled in Add")
+ }
+ val = ifaceData(ncase.Pos, s.facename, singleType)
+ }
+ l := []*Node{
+ nodl(ncase.Pos, ODCL, caseVar, nil),
+ nodl(ncase.Pos, OAS, caseVar, val),
+ }
+ typecheckslice(l, ctxStmt)
+ body.Append(l...)
+ }
+ body.Append(ncase.Nbody.Slice()...)
+ body.Append(br)
+ }
+ sw.List.Set(nil)
+
+ if defaultGoto == nil {
+ defaultGoto = br
+ }
+ if nilGoto == nil {
+ nilGoto = defaultGoto
+ }
+ ifNil.Nbody.Set1(nilGoto)
+
+ s.Emit(&sw.Nbody)
+ sw.Nbody.Append(defaultGoto)
+ sw.Nbody.AppendNodes(&body)
+
+ walkstmtlist(sw.Nbody.Slice())
+}
+
+// A typeSwitch walks a type switch.
+type typeSwitch struct {
+ // Temporary variables (i.e., ONAMEs) used by type switch dispatch logic:
+ facename *Node // value being type-switched on
+ hashname *Node // type hash of the value being type-switched on
+ okname *Node // boolean used for comma-ok type assertions
+
+ done Nodes
+ clauses []typeClause
+}
+
+type typeClause struct {
+ hash uint32
+ body Nodes
+}
+
+func (s *typeSwitch) Add(pos src.XPos, typ *types.Type, caseVar, jmp *Node) {
+ var body Nodes
+ if caseVar != nil {
+ l := []*Node{
+ nodl(pos, ODCL, caseVar, nil),
+ nodl(pos, OAS, caseVar, nil),
+ }
+ typecheckslice(l, ctxStmt)
+ body.Append(l...)
+ } else {
+ caseVar = nblank
+ }
+
+ // cv, ok = iface.(type)
+ as := nodl(pos, OAS2, nil, nil)
+ as.List.Set2(caseVar, s.okname) // cv, ok =
+ dot := nodl(pos, ODOTTYPE, s.facename, nil)
+ dot.Type = typ // iface.(type)
+ as.Rlist.Set1(dot)
+ as = typecheck(as, ctxStmt)
+ as = walkexpr(as, &body)
+ body.Append(as)
+
+ // if ok { goto label }
+ nif := nodl(pos, OIF, nil, nil)
+ nif.Left = s.okname
+ nif.Nbody.Set1(jmp)
+ body.Append(nif)
+
+ if !typ.IsInterface() {
+ s.clauses = append(s.clauses, typeClause{
+ hash: typehash(typ),
+ body: body,
+ })
+ return
+ }
+
+ s.flush()
+ s.done.AppendNodes(&body)
+}
+
+func (s *typeSwitch) Emit(out *Nodes) {
+ s.flush()
+ out.AppendNodes(&s.done)
+}
+
+func (s *typeSwitch) flush() {
+ cc := s.clauses
+ s.clauses = nil
+ if len(cc) == 0 {
+ return
+ }
+
+ sort.Slice(cc, func(i, j int) bool { return cc[i].hash < cc[j].hash })
+
+ // Combine adjacent cases with the same hash.
+ merged := cc[:1]
+ for _, c := range cc[1:] {
+ last := &merged[len(merged)-1]
+ if last.hash == c.hash {
+ last.body.AppendNodes(&c.body)
+ } else {
+ merged = append(merged, c)
+ }
+ }
+ cc = merged
+
+ binarySearch(len(cc), &s.done,
+ func(i int) *Node {
+ return nod(OLE, s.hashname, nodintconst(int64(cc[i-1].hash)))
+ },
+ func(i int, nif *Node) {
+ // TODO(mdempsky): Omit hash equality check if
+ // there's only one type.
+ c := cc[i]
+ nif.Left = nod(OEQ, s.hashname, nodintconst(int64(c.hash)))
+ nif.Nbody.AppendNodes(&c.body)
+ },
+ )
+}
+
+// binarySearch constructs a binary search tree for handling n cases,
+// and appends it to out. It's used for efficiently implementing
+// switch statements.
+//
+// less(i) should return a boolean expression. If it evaluates true,
+// then cases before i will be tested; otherwise, cases i and later.
+//
+// base(i, nif) should setup nif (an OIF node) to test case i. In
+// particular, it should set nif.Left and nif.Nbody.
+func binarySearch(n int, out *Nodes, less func(i int) *Node, base func(i int, nif *Node)) {
+ const binarySearchMin = 4 // minimum number of cases for binary search
+
+ var do func(lo, hi int, out *Nodes)
+ do = func(lo, hi int, out *Nodes) {
+ n := hi - lo
+ if n < binarySearchMin {
+ for i := lo; i < hi; i++ {
+ nif := nod(OIF, nil, nil)
+ base(i, nif)
+ lineno = lineno.WithNotStmt()
+ nif.Left = typecheck(nif.Left, ctxExpr)
+ nif.Left = defaultlit(nif.Left, nil)
+ out.Append(nif)
+ out = &nif.Rlist
+ }
+ return
+ }
+
+ half := lo + n/2
+ nif := nod(OIF, nil, nil)
+ nif.Left = less(half)
+ lineno = lineno.WithNotStmt()
+ nif.Left = typecheck(nif.Left, ctxExpr)
+ nif.Left = defaultlit(nif.Left, nil)
+ do(lo, half, &nif.Nbody)
+ do(half, hi, &nif.Rlist)
+ out.Append(nif)
+ }
+
+ do(0, n, out)
+}