switch: collect case ranges first, then generate code

Collect cases first, then emit lookup code. Elliminates
jumps to implement pass-through and jumps to link cases.
This commit is contained in:
Pavlas, Zdenek 2016-09-21 08:35:29 -07:00
parent d5a1e32ac3
commit fc0fc6aba3

114
tccgen.c
View File

@ -72,6 +72,13 @@ ST_DATA const char *funcname;
ST_DATA CType char_pointer_type, func_old_type, int_type, size_type; ST_DATA CType char_pointer_type, func_old_type, int_type, size_type;
ST_DATA struct switch_t {
struct case_t {
int v1, v2, sym;
} **p; int n; /* list of case ranges */
int def_sym; /* default symbol */
} *cur_switch; /* current switch */
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
static void gen_cast(CType *type); static void gen_cast(CType *type);
static inline CType *pointed_type(CType *type); static inline CType *pointed_type(CType *type);
@ -80,7 +87,7 @@ static int parse_btype(CType *type, AttributeDef *ad);
static void type_decl(CType *type, AttributeDef *ad, int *v, int td); static void type_decl(CType *type, AttributeDef *ad, int *v, int td);
static void parse_expr_type(CType *type); static void parse_expr_type(CType *type);
static void decl_initializer(CType *type, Section *sec, unsigned long c, int first, int size_only); static void decl_initializer(CType *type, Section *sec, unsigned long c, int first, int size_only);
static void block(int *bsym, int *csym, int *case_sym, int *def_sym, int case_reg, int is_expr); static void block(int *bsym, int *csym, int is_expr);
static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, int has_init, int v, int scope); static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, int has_init, int v, int scope);
static int decl0(int l, int is_for_loop_init); static int decl0(int l, int is_for_loop_init);
static void expr_eq(void); static void expr_eq(void);
@ -3859,7 +3866,7 @@ ST_FUNC void unary(void)
save_regs(0); save_regs(0);
/* statement expression : we do not accept break/continue /* statement expression : we do not accept break/continue
inside as GCC does */ inside as GCC does */
block(NULL, NULL, NULL, NULL, 0, 1); block(NULL, NULL, 1);
skip(')'); skip(')');
} else { } else {
gexpr(); gexpr();
@ -4876,8 +4883,10 @@ static void label_or_decl(int l)
decl(l); decl(l);
} }
static void block(int *bsym, int *csym, int *case_sym, int *def_sym, static int case_cmp(const void *a, const void *b)
int case_reg, int is_expr) { return (*(struct case_t**) a)->v1 - (*(struct case_t**) b)->v1; }
static void block(int *bsym, int *csym, int is_expr)
{ {
int a, b, c, d; int a, b, c, d;
Sym *s; Sym *s;
@ -4903,13 +4912,13 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
gexpr(); gexpr();
skip(')'); skip(')');
a = gvtst(1, 0); a = gvtst(1, 0);
block(bsym, csym, case_sym, def_sym, case_reg, 0); block(bsym, csym, 0);
c = tok; c = tok;
if (c == TOK_ELSE) { if (c == TOK_ELSE) {
next(); next();
d = gjmp(0); d = gjmp(0);
gsym(a); gsym(a);
block(bsym, csym, case_sym, def_sym, case_reg, 0); block(bsym, csym, 0);
gsym(d); /* patch else jmp */ gsym(d); /* patch else jmp */
} else } else
gsym(a); gsym(a);
@ -4923,7 +4932,7 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
a = gvtst(1, 0); a = gvtst(1, 0);
b = 0; b = 0;
++local_scope; ++local_scope;
block(&a, &b, case_sym, def_sym, case_reg, 0); block(&a, &b, 0);
--local_scope; --local_scope;
if(!nocode_wanted) if(!nocode_wanted)
gjmp_addr(d); gjmp_addr(d);
@ -4960,7 +4969,7 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
if (tok != '}') { if (tok != '}') {
if (is_expr) if (is_expr)
vpop(); vpop();
block(bsym, csym, case_sym, def_sym, case_reg, is_expr); block(bsym, csym, is_expr);
} }
} }
/* pop locally defined labels */ /* pop locally defined labels */
@ -5115,7 +5124,7 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
gsym(e); gsym(e);
} }
skip(')'); skip(')');
block(&a, &b, case_sym, def_sym, case_reg, 0); block(&a, &b, 0);
if(!nocode_wanted) if(!nocode_wanted)
gjmp_addr(c); gjmp_addr(c);
gsym(a); gsym(a);
@ -5130,7 +5139,7 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
b = 0; b = 0;
d = ind; d = ind;
vla_sp_restore(); vla_sp_restore();
block(&a, &b, case_sym, def_sym, case_reg, 0); block(&a, &b, 0);
skip(TOK_WHILE); skip(TOK_WHILE);
skip('('); skip('(');
gsym(b); gsym(b);
@ -5142,58 +5151,65 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
skip(';'); skip(';');
} else } else
if (tok == TOK_SWITCH) { if (tok == TOK_SWITCH) {
struct switch_t *saved, sw;
next(); next();
skip('('); skip('(');
gexpr(); gexpr();
/* XXX: other types than integer */ /* XXX: other types than integer */
case_reg = gv(RC_INT); c = gv(RC_INT);
vpop(); vpop();
skip(')'); skip(')');
a = 0; a = 0;
b = gjmp(0); /* jump to first case */ b = gjmp(0); /* jump to first case */
c = 0; sw.p = NULL; sw.n = 0; sw.def_sym = 0;
block(&a, csym, &b, &c, case_reg, 0); saved = cur_switch;
/* if no default, jmp after switch */ cur_switch = &sw; block(&a, csym, 0);
if (c == 0) cur_switch = saved;
c = ind; a = gjmp(a); /* add implicit break */
/* default label */ gsym(b);
gsym_addr(b, c);
qsort(sw.p, sw.n, sizeof(void*), case_cmp);
for (b = 0; b < sw.n; b++) {
int v = sw.p[b]->v1;
if (b && v <= d)
tcc_error("duplicate case value");
d = sw.p[b]->v2;
vseti(c, 0);
vpushi(v);
if (v == d) {
gen_op(TOK_EQ);
gsym_addr(gtst(0, 0), sw.p[b]->sym);
} else {
int e;
gen_op(TOK_GE);
e = gtst(1, 0);
vseti(c, 0);
vpushi(d);
gen_op(TOK_LE);
gsym_addr(gtst(0, 0), sw.p[b]->sym);
gsym(e);
}
}
if (sw.def_sym)
gjmp_addr(sw.def_sym);
/* break label */ /* break label */
gsym(a); gsym(a);
} else } else
if (tok == TOK_CASE) { if (tok == TOK_CASE) {
int v1, v2; struct case_t *cr = tcc_malloc(sizeof(struct case_t));
if (!case_sym) if (!cur_switch)
expect("switch"); expect("switch");
next(); next();
v1 = expr_const(); cr->v1 = cr->v2 = expr_const();
v2 = v1;
if (gnu_ext && tok == TOK_DOTS) { if (gnu_ext && tok == TOK_DOTS) {
next(); next();
v2 = expr_const(); cr->v2 = expr_const();
if (v2 < v1) if (cr->v2 < cr->v1)
tcc_warning("empty case range"); tcc_warning("empty case range");
} }
/* since a case is like a label, we must skip it with a jmp */ cr->sym = ind;
b = gjmp(0); dynarray_add((void***) &cur_switch->p, &cur_switch->n, cr);
gsym(*case_sym);
vseti(case_reg, 0);
vdup();
vpushi(v1);
if (v1 == v2) {
gen_op(TOK_EQ);
*case_sym = gtst(1, 0);
} else {
gen_op(TOK_GE);
*case_sym = gtst(1, 0);
vseti(case_reg, 0);
vpushi(v2);
gen_op(TOK_LE);
*case_sym = gtst(1, *case_sym);
}
case_reg = gv(RC_INT);
vpop();
gsym(b);
skip(':'); skip(':');
is_expr = 0; is_expr = 0;
goto block_after_label; goto block_after_label;
@ -5201,11 +5217,11 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
if (tok == TOK_DEFAULT) { if (tok == TOK_DEFAULT) {
next(); next();
skip(':'); skip(':');
if (!def_sym) if (!cur_switch)
expect("switch"); expect("switch");
if (*def_sym) if (cur_switch->def_sym)
tcc_error("too many 'default'"); tcc_error("too many 'default'");
*def_sym = ind; cur_switch->def_sym = ind;
is_expr = 0; is_expr = 0;
goto block_after_label; goto block_after_label;
} else } else
@ -5261,7 +5277,7 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
} else { } else {
if (is_expr) if (is_expr)
vpop(); vpop();
block(bsym, csym, case_sym, def_sym, case_reg, is_expr); block(bsym, csym, is_expr);
} }
} else { } else {
/* expression case */ /* expression case */
@ -6177,7 +6193,7 @@ static void gen_function(Sym *sym)
} }
#endif #endif
rsym = 0; rsym = 0;
block(NULL, NULL, NULL, NULL, 0, 0); block(NULL, NULL, 0);
gsym(rsym); gsym(rsym);
gfunc_epilog(); gfunc_epilog();
cur_text_section->data_offset = ind; cur_text_section->data_offset = ind;