// 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. #include "go.h" enum { Snorm = 0, Strue, Sfalse, Stype, Tdefault, // default case Texprconst, // normal constant case Texprvar, // normal variable case Ttypenil, // case nil Ttypeconst, // type hashes Ttypevar, // interface type Ncase = 4, // count needed to split }; typedef struct Case Case; struct Case { Node* node; // points at case statement uint32 hash; // hash of a type switch uint8 type; // type of case uint8 diag; // suppress multiple diagnostics uint16 ordinal; // position in switch Case* link; // linked list to link }; #define C ((Case*)nil) void dumpcase(Case *c0) { Case *c; for(c=c0; c!=C; c=c->link) { switch(c->type) { case Tdefault: print("case-default\n"); print(" ord=%d\n", c->ordinal); break; case Texprconst: print("case-exprconst\n"); print(" ord=%d\n", c->ordinal); break; case Texprvar: print("case-exprvar\n"); print(" ord=%d\n", c->ordinal); print(" op=%O\n", c->node->left->op); break; case Ttypenil: print("case-typenil\n"); print(" ord=%d\n", c->ordinal); break; case Ttypeconst: print("case-typeconst\n"); print(" ord=%d\n", c->ordinal); print(" hash=%ux\n", c->hash); break; case Ttypevar: print("case-typevar\n"); print(" ord=%d\n", c->ordinal); break; default: print("case-???\n"); print(" ord=%d\n", c->ordinal); print(" op=%O\n", c->node->left->op); print(" hash=%ux\n", c->hash); break; } } print("\n"); } static int ordlcmp(Case *c1, Case *c2) { // 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; } static int exprcmp(Case *c1, Case *c2) { int ct, n; Node *n1, *n2; // sort non-constants last if(c1->type != Texprconst) return +1; if(c2->type != Texprconst) return -1; n1 = c1->node->left; n2 = c2->node->left; ct = n1->val.ctype; if(ct != n2->val.ctype) fatal("exprcmp"); // sort by constant value n = 0; switch(ct) { case CTFLT: n = mpcmpfltflt(n1->val.u.fval, n2->val.u.fval); break; case CTINT: n = mpcmpfixfix(n1->val.u.xval, n2->val.u.xval); break; case CTSTR: n = cmpslit(n1, n2); break; } return n; } static int typecmp(Case *c1, Case *c2) { // sort non-constants last if(c1->type != Ttypeconst) return +1; if(c2->type != Ttypeconst) return -1; // sort by hash code if(c1->hash > c2->hash) return +1; if(c1->hash < c2->hash) return -1; return 0; } static Case* csort(Case *l, int(*f)(Case*, Case*)) { Case *l1, *l2, *le; if(l == C || l->link == C) return l; l1 = l; l2 = l; for(;;) { l2 = l2->link; if(l2 == C) break; l2 = l2->link; if(l2 == C) break; l1 = l1->link; } l2 = l1->link; l1->link = C; l1 = csort(l, f); l2 = csort(l2, f); /* set up lead element */ if((*f)(l1, l2) < 0) { l = l1; l1 = l1->link; } else { l = l2; l2 = l2->link; } le = l; for(;;) { if(l1 == C) { while(l2) { le->link = l2; le = l2; l2 = l2->link; } le->link = C; break; } if(l2 == C) { while(l1) { le->link = l1; le = l1; l1 = l1->link; } break; } if((*f)(l1, l2) < 0) { le->link = l1; le = l1; l1 = l1->link; } else { le->link = l2; le = l2; l2 = l2->link; } } le->link = C; return l; } /* * walktype */ Type* sw0(Node *c, Type *place, int arg) { if(c == N) return T; switch(c->op) { default: if(arg == Stype) { yyerror("inappropriate case for a type switch"); return T; } walktype(c, Erv); break; case OTYPESW: if(arg != Stype) yyerror("inappropriate type case"); break; case OAS: yyerror("inappropriate assignment in a case statement"); break; } return T; } /* * return the first type */ Type* sw1(Node *c, Type *place, int arg) { if(place == T) return c->type; return place; } /* * return a suitable type */ Type* sw2(Node *c, Type *place, int arg) { return types[TINT]; // botch } /* * check that switch type * is compat with all the cases */ Type* sw3(Node *c, Type *place, int arg) { if(place == T) return c->type; if(c->type == T) c->type = place; convlit(c, place); if(!ascompat(place, c->type)) badtype(OSWITCH, place, c->type); return place; } /* * over all cases, call paramenter function. * four passes of these are used to allocate * types to cases and switch */ Type* walkcases(Node *sw, Type*(*call)(Node*, Type*, int arg), int arg) { Iter save; Node *n; Type *place; int32 lno; lno = setlineno(sw); place = call(sw->ntest, T, arg); n = listfirst(&save, &sw->nbody->left); if(n == N || n->op == OEMPTY) return T; loop: if(n == N) { lineno = lno; return place; } if(n->op != OCASE) fatal("walkcases: not case %O\n", n->op); if(n->left != N) { setlineno(n->left); place = call(n->left, place, arg); } n = listnext(&save); goto loop; } Node* newlabel() { static int label; label++; snprint(namebuf, sizeof(namebuf), "%.6d", label); return newname(lookup(namebuf)); } /* * build separate list of statements and cases * make labels between cases and statements * deal with fallthrough, break, unreachable statements */ void casebody(Node *sw) { Iter save; Node *os, *oc, *n, *c; Node *cas, *stat, *def; Node *go, *br; int32 lno; lno = setlineno(sw); n = listfirst(&save, &sw->nbody); if(n == N || n->op == OEMPTY) { sw->nbody = nod(OLIST, N, N); return; } cas = N; // cases stat = N; // statements def = N; // defaults os = N; // last statement oc = N; // last case br = nod(OBREAK, N, N); loop: if(n == N) { if(oc == N && os != N) yyerror("first switch statement must be a case"); stat = list(stat, br); cas = list(cas, def); sw->nbody = nod(OLIST, rev(cas), rev(stat)); //dump("case", sw->nbody->left); //dump("stat", sw->nbody->right); lineno = lno; return; } lno = setlineno(n); if(n->op != OXCASE) { stat = list(stat, n); os = n; goto next; } n->op = OCASE; if(oc == N && os != N) yyerror("first switch statement must be a case"); // botch - shouldnt fall thru declaration if(os != N && os->op == OXFALL) os->op = OFALL; else stat = list(stat, br); go = nod(OGOTO, newlabel(), N); c = n->left; if(c == N) { if(def != N) yyerror("more than one default case"); // reuse original default case n->right = go; def = n; } // expand multi-valued cases for(; c!=N; c=c->right) { if(c->op != OLIST) { // reuse original case n->left = c; n->right = go; cas = list(cas, n); break; } cas = list(cas, nod(OCASE, c->left, go)); } stat = list(stat, nod(OLABEL, go->left, N)); oc = n; os = N; goto next; next: n = listnext(&save); goto loop; } Case* mkcaselist(Node *sw, int arg) { Iter save; Node *n; Case *c, *c1; int ord; c = C; ord = 0; n = listfirst(&save, &sw->nbody->left); loop: if(n == N) goto done; c1 = mal(sizeof(*c1)); c1->link = c; c = c1; ord++; c->ordinal = ord; c->node = n; if(n->left == N) { c->type = Tdefault; goto next; } switch(arg) { case Stype: c->hash = 0; if(n->left->left == N) { c->type = Ttypenil; goto next; } if(istype(n->left->left->type, TINTER)) { c->type = Ttypevar; goto next; } c->hash = typehash(n->left->left->type, 1, 0); c->type = Ttypeconst; goto next; case Snorm: case Strue: case Sfalse: c->type = Texprvar; switch(consttype(n->left)) { case CTFLT: case CTINT: case CTSTR: c->type = Texprconst; } goto next; } next: n = listnext(&save); goto loop; done: if(c == C) return C; // sort by value and diagnose duplicate cases switch(arg) { case Stype: c = csort(c, typecmp); for(c1=c; c1->link!=C; c1=c1->link) { if(typecmp(c1, c1->link) != 0) continue; setlineno(c1->link->node); yyerror("duplicate case in switch"); print(" previous case at %L\n", c1->node->lineno); } break; case Snorm: case Strue: case Sfalse: c = csort(c, exprcmp); for(c1=c; c1->link!=C; c1=c1->link) { if(exprcmp(c1, c1->link) != 0) continue; setlineno(c1->link->node); yyerror("duplicate case in switch"); print(" previous case at %L\n", c1->node->lineno); } break; } // put list back in processing order c = csort(c, ordlcmp); return c; } static Node* exprname; Node* exprbsw(Case *c0, int ncase, int arg) { Node *cas; Node *a, *n; Case *c; int i, half; cas = N; if(ncase < Ncase) { for(i=0; inode; switch(arg) { case Strue: a = nod(OIF, N, N); a->ntest = n->left; // if val a->nbody = n->right; // then goto l break; case Sfalse: a = nod(OIF, N, N); a->ntest = nod(ONOT, n->left, N); // if !val a->nbody = n->right; // then goto l break; default: a = nod(OIF, N, N); a->ntest = nod(OEQ, exprname, n->left); // if name == val a->nbody = n->right; // then goto l break; } cas = list(cas, a); c0 = c0->link; } return cas; } // find the middle and recur c = c0; half = ncase>>1; for(i=1; ilink; a = nod(OIF, N, N); a->ntest = nod(OLE, exprname, c->node->left); a->nbody = exprbsw(c0, half, arg); a->nelse = exprbsw(c->link, ncase-half, arg); return a; } /* * normal (expression) switch. * rebulid case statements into if .. goto */ void exprswitch(Node *sw) { Node *def, *cas; Node *a; Case *c0, *c, *c1; Type *t; int arg, ncase; arg = Snorm; if(isconst(sw->ntest, CTBOOL)) { arg = Strue; if(sw->ntest->val.u.bval == 0) arg = Sfalse; } walktype(sw->ntest, Erv); /* * pass 0,1,2,3 * walk the cases as appropriate for switch type */ walkcases(sw, sw0, arg); t = sw->ntest->type; if(t == T) t = walkcases(sw, sw1, arg); if(t == T) t = walkcases(sw, sw2, arg); if(t == T) return; walkcases(sw, sw3, arg); convlit(sw->ntest, t); /* * convert the switch into OIF statements */ exprname = N; cas = N; if(arg != Strue && arg != Sfalse) { exprname = nod(OXXX, N, N); tempname(exprname, sw->ntest->type); cas = nod(OAS, exprname, sw->ntest); } c0 = mkcaselist(sw, arg); if(c0 != C && c0->type == Tdefault) { def = c0->node->right; c0 = c0->link; } else { def = nod(OBREAK, N, N); } loop: if(c0 == C) { cas = list(cas, def); sw->nbody->left = rev(cas); walkstate(sw->nbody); return; } // deal with the variables one-at-a-time if(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!=C; c=c->link) { if(c->link->type != Texprconst) break; ncase++; } // break the chain at the count c1 = c->link; c->link = C; // sort and compile constants c0 = csort(c0, exprcmp); a = exprbsw(c0, ncase, arg); cas = list(cas, a); c0 = c1; goto loop; } static Node* hashname; static Node* facename; static Node* boolname; Node* typeone(Node *t) { Node *a, *b; a = t->left->left; // var a = nod(OLIST, a, boolname); // var,bool b = nod(ODOTTYPE, facename, N); b->type = t->left->left->type; // interface.(type) a = nod(OAS, a, b); // var,bool = interface.(type) b = nod(OIF, N, N); b->ntest = boolname; b->nbody = t->right; // if bool { goto l } return list(a, b); } Node* typebsw(Case *c0, int ncase) { Node *cas; Node *a, *n; Case *c; int i, half; Val v; cas = N; if(ncase < Ncase) { for(i=0; inode; switch(c0->type) { case Ttypenil: v.ctype = CTNIL; a = nod(OIF, N, N); a->ntest = nod(OEQ, facename, nodlit(v)); a->nbody = n->right; // if i==nil { goto l } cas = list(cas, a); break; case Ttypevar: a = typeone(n); cas = list(cas, a); break; case Ttypeconst: a = nod(OIF, N, N); a->ntest = nod(OEQ, hashname, nodintconst(c0->hash)); a->nbody = rev(typeone(n)); cas = list(cas, a); break; } c0 = c0->link; } return cas; } // find the middle and recur c = c0; half = ncase>>1; for(i=1; ilink; a = nod(OIF, N, N); a->ntest = nod(OLE, hashname, nodintconst(c->hash)); a->nbody = typebsw(c0, half); a->nelse = typebsw(c->link, ncase-half); return a; } /* * convert switch of the form * switch v := i.(type) { case t1: ..; case t2: ..; } * into if statements */ void typeswitch(Node *sw) { Node *cas, *def; Node *a; Case *c, *c0, *c1; int ncase; if(sw->ntest == nil) return; if(sw->ntest->right == nil) { setlineno(sw); yyerror("type switch must have an assignment"); return; } walktype(sw->ntest->right, Erv); if(!istype(sw->ntest->right->type, TINTER)) { yyerror("type switch must be on an interface"); return; } walkcases(sw, sw0, Stype); cas = N; /* * predeclare temporary variables * and the boolean var */ facename = nod(OXXX, N, N); tempname(facename, sw->ntest->right->type); a = nod(OAS, facename, sw->ntest->right); cas = list(cas, a); boolname = nod(OXXX, N, N); tempname(boolname, types[TBOOL]); hashname = nod(OXXX, N, N); tempname(hashname, types[TUINT32]); a = syslook("ifacethash", 1); argtype(a, sw->ntest->right->type); a = nod(OCALL, a, facename); a = nod(OAS, hashname, a); cas = list(cas, a); c0 = mkcaselist(sw, Stype); if(c0 != C && c0->type == Tdefault) { def = c0->node->right; c0 = c0->link; } else { def = nod(OBREAK, N, N); } loop: if(c0 == C) { cas = list(cas, def); sw->nbody->left = rev(cas); walkstate(sw->nbody); return; } // deal with the variables one-at-a-time if(c0->type != Ttypeconst) { a = typebsw(c0, 1); cas = list(cas, a); c0 = c0->link; goto loop; } // do binary search on run of constants ncase = 1; for(c=c0; c->link!=C; c=c->link) { if(c->link->type != Ttypeconst) break; ncase++; } // break the chain at the count c1 = c->link; c->link = C; // sort and compile constants c0 = csort(c0, typecmp); a = typebsw(c0, ncase); cas = list(cas, a); c0 = c1; goto loop; } void walkswitch(Node *sw) { /* * reorder the body into (OLIST, cases, statements) * cases have OGOTO into statements. * both have inserted OBREAK statements */ walkstate(sw->ninit); if(sw->ntest == N) sw->ntest = nodbool(1); casebody(sw); if(sw->ntest->op == OTYPESW) { typeswitch(sw); return; } exprswitch(sw); }