Fix 64bit enums and switch cases

See testcases.  We now support 64bit case constants.  At the same time
also 64bit enum constants on L64 platforms (otherwise the Sym struct
isn't large enough for now).  The testcase also checks for various
cases where sign/zero extension was confused.
This commit is contained in:
Michael Matz 2016-11-06 05:02:11 +01:00
parent 3e77bfb6e9
commit 22f5fccc2c
3 changed files with 196 additions and 41 deletions

6
tcc.h
View File

@ -1103,11 +1103,11 @@ ST_FUNC void cstr_reset(CString *cstr);
ST_INLN void sym_free(Sym *sym);
ST_FUNC Sym *sym_push2(Sym **ps, int v, int t, long c);
ST_FUNC Sym *sym_find2(Sym *s, int v);
ST_FUNC Sym *sym_push(int v, CType *type, int r, int c);
ST_FUNC Sym *sym_push(int v, CType *type, int r, long c);
ST_FUNC void sym_pop(Sym **ptop, Sym *b, int keep);
ST_INLN Sym *struct_find(int v);
ST_INLN Sym *sym_find(int v);
ST_FUNC Sym *global_identifier_push(int v, int t, int c);
ST_FUNC Sym *global_identifier_push(int v, int t, long c);
ST_FUNC void tcc_open_bf(TCCState *s1, const char *filename, int initlen);
ST_FUNC int tcc_open(TCCState *s1, const char *filename);
@ -1271,7 +1271,7 @@ ST_FUNC void test_lvalue(void);
ST_FUNC void swap(int *p, int *q);
ST_FUNC void vpushi(int v);
ST_FUNC Sym *external_global_sym(int v, CType *type, int r);
ST_FUNC void vset(CType *type, int r, int v);
ST_FUNC void vset(CType *type, int r, long v);
ST_FUNC void vswap(void);
ST_FUNC void vpush_global_sym(CType *type, int v);
ST_FUNC void vrote(SValue *e, int n);

114
tccgen.c
View File

@ -62,7 +62,8 @@ 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;
int64_t v1, v2;
int sym;
} **p; int n; /* list of case ranges */
int def_sym; /* default symbol */
} *cur_switch; /* current switch */
@ -86,6 +87,7 @@ static void vla_sp_restore(void);
static void vla_sp_restore_root(void);
static int is_compatible_parameter_types(CType *type1, CType *type2);
static void expr_type(CType *type);
static inline int64_t expr_const64(void);
ST_FUNC void vpush64(int ty, unsigned long long v);
ST_FUNC void vpush(CType *type);
ST_FUNC int gvtst(int inv, int t);
@ -434,7 +436,7 @@ ST_INLN Sym *sym_find(int v)
}
/* push a given symbol on the symbol stack */
ST_FUNC Sym *sym_push(int v, CType *type, int r, int c)
ST_FUNC Sym *sym_push(int v, CType *type, int r, long c)
{
Sym *s, **ps;
TokenSym *ts;
@ -466,7 +468,7 @@ ST_FUNC Sym *sym_push(int v, CType *type, int r, int c)
}
/* push a global identifier */
ST_FUNC Sym *global_identifier_push(int v, int t, int c)
ST_FUNC Sym *global_identifier_push(int v, int t, long c)
{
Sym *s, **ps;
s = sym_push2(&global_stack, v, t, c);
@ -698,7 +700,7 @@ ST_FUNC void vpush_global_sym(CType *type, int v)
vpushsym(type, external_global_sym(v, type, 0));
}
ST_FUNC void vset(CType *type, int r, int v)
ST_FUNC void vset(CType *type, int r, long v)
{
CValue cval;
@ -1716,6 +1718,9 @@ static void gen_opic(int op)
default:
goto general_case;
}
if (t1 != VT_LLONG && (PTR_SIZE != 8 || t1 != VT_PTR))
l1 = ((uint32_t)l1 |
(v1->type.t & VT_UNSIGNED ? 0 : -(l1 & 0x80000000)));
v1->c.i = l1;
vtop--;
} else {
@ -3248,7 +3253,8 @@ static Sym * find_field (CType *type, int v)
/* enum/struct/union declaration. u is either VT_ENUM or VT_STRUCT */
static void struct_decl(CType *type, AttributeDef *ad, int u)
{
int a, v, size, align, maxalign, c, offset, flexible, extra_bytes;
int a, v, size, align, maxalign, offset, flexible, extra_bytes;
long c;
int bit_size, bit_pos, bsize, bt, lbit_pos, prevbt;
Sym *s, *ss, *ass, **ps;
AttributeDef ad1;
@ -3293,7 +3299,9 @@ static void struct_decl(CType *type, AttributeDef *ad, int u)
/* non empty enums are not allowed */
if (a == TOK_ENUM) {
int seen_neg = 0;
int seen_wide = 0;
for(;;) {
CType *t = &int_type;
v = tok;
if (v < TOK_UIDENT)
expect("identifier");
@ -3304,12 +3312,23 @@ static void struct_decl(CType *type, AttributeDef *ad, int u)
next();
if (tok == '=') {
next();
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
c = expr_const64();
#else
/* We really want to support long long enums
on i386 as well, but the Sym structure only
holds a 'long' for associated constants,
and enlarging it would bump its size (no
available padding). So punt for now. */
c = expr_const();
#endif
}
if (c < 0)
seen_neg = 1;
if (c != (int)c && (unsigned long)c != (unsigned int)c)
seen_wide = 1, t = &size_type;
/* enum symbols have static storage */
ss = sym_push(v, &int_type, VT_CONST, c);
ss = sym_push(v, t, VT_CONST, c);
ss->type.t |= VT_STATIC;
if (tok != ',')
break;
@ -3321,7 +3340,7 @@ static void struct_decl(CType *type, AttributeDef *ad, int u)
}
if (!seen_neg)
s->a.unsigned_enum = 1;
s->c = type_size(&int_type, &align);
s->c = type_size(seen_wide ? &size_type : &int_type, &align);
skip('}');
} else {
maxalign = 1;
@ -4355,10 +4374,11 @@ ST_FUNC void unary(void)
break;
case TOK_builtin_choose_expr:
{
int saved_nocode_wanted, c;
int saved_nocode_wanted;
int64_t c;
next();
skip('(');
c = expr_const();
c = expr_const64();
skip(',');
if (!c) {
saved_nocode_wanted = nocode_wanted;
@ -5272,9 +5292,9 @@ static void expr_const1(void)
}
/* parse an integer constant and return its value. */
ST_FUNC int expr_const(void)
static inline int64_t expr_const64(void)
{
int c;
int64_t c;
expr_const1();
if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) != VT_CONST)
expect("constant expression");
@ -5283,6 +5303,18 @@ ST_FUNC int expr_const(void)
return c;
}
/* parse an integer constant and return its value.
Complain if it doesn't fit 32bit (signed or unsigned). */
ST_FUNC int expr_const(void)
{
int c;
int64_t wc = expr_const64();
c = wc;
if (c != wc && (unsigned)c != wc)
tcc_error("constant exceeds 32 bit");
return c;
}
/* return the label token if current token is a label, otherwise
return zero */
static int is_label(void)
@ -5325,34 +5357,36 @@ static void label_or_decl(int l)
static int case_cmp(const void *pa, const void *pb)
{
int a = (*(struct case_t**) pa)->v1;
int b = (*(struct case_t**) pb)->v1;
int64_t a = (*(struct case_t**) pa)->v1;
int64_t b = (*(struct case_t**) pb)->v1;
return a < b ? -1 : a > b;
}
static int gcase(struct case_t **base, int len, int case_reg, int *bsym)
static void gcase(struct case_t **base, int len, int *bsym)
{
struct case_t *p;
int e;
int ll = (vtop->type.t & VT_BTYPE) == VT_LLONG;
gv(RC_INT);
while (len > 4) {
/* binary search */
p = base[len/2];
vseti(case_reg, 0);
vdup();
vpushi(p->v2);
if (ll)
vpushll(p->v2);
else
vpushi(p->v2);
gen_op(TOK_LE);
e = gtst(1, 0);
case_reg = gv(RC_INT);
vpop();
vseti(case_reg, 0);
vdup();
vpushi(p->v1);
if (ll)
vpushll(p->v1);
else
vpushi(p->v1);
gen_op(TOK_GE);
gtst_addr(0, p->sym); /* v1 <= x <= v2 */
case_reg = gv(RC_INT);
vpop();
/* x < v1 */
case_reg = gcase(base, len/2, case_reg, bsym);
gcase(base, len/2, bsym);
if (cur_switch->def_sym)
gjmp_addr(cur_switch->def_sym);
else
@ -5365,28 +5399,27 @@ static int gcase(struct case_t **base, int len, int case_reg, int *bsym)
/* linear scan */
while (len--) {
p = *base++;
vseti(case_reg, 0);
vdup();
vpushi(p->v2);
if (ll)
vpushll(p->v2);
else
vpushi(p->v2);
if (p->v1 == p->v2) {
gen_op(TOK_EQ);
gtst_addr(0, p->sym);
} else {
gen_op(TOK_LE);
e = gtst(1, 0);
case_reg = gv(RC_INT);
vpop();
vseti(case_reg, 0);
vdup();
vpushi(p->v1);
if (ll)
vpushll(p->v1);
else
vpushi(p->v1);
gen_op(TOK_GE);
gtst_addr(0, p->sym);
gsym(e);
}
case_reg = gv(RC_INT);
vpop();
}
return case_reg;
}
static void block(int *bsym, int *csym, int is_expr)
@ -5675,13 +5708,12 @@ static void block(int *bsym, int *csym, int is_expr)
if (tok == TOK_SWITCH) {
struct switch_t *saved, sw;
int saved_nocode_wanted = nocode_wanted;
SValue switchval;
next();
skip('(');
gexpr();
/* XXX: other types than integer */
c = gv(RC_INT);
vpop();
skip(')');
switchval = *vtop--;
a = 0;
b = gjmp(0); /* jump to first case */
sw.p = NULL; sw.n = 0; sw.def_sym = 0;
@ -5697,7 +5729,13 @@ static void block(int *bsym, int *csym, int is_expr)
for (b = 1; b < sw.n; b++)
if (sw.p[b - 1]->v2 >= sw.p[b]->v1)
tcc_error("duplicate case value");
gcase(sw.p, sw.n, c, &a);
/* Our switch table sorting is signed, so the compared
value needs to be as well when it's 64bit. */
if ((switchval.type.t & VT_BTYPE) == VT_LLONG)
switchval.type.t &= ~VT_UNSIGNED;
vpushv(&switchval);
gcase(sw.p, sw.n, &a);
vpop();
if (sw.def_sym)
gjmp_addr(sw.def_sym);
}
@ -5712,10 +5750,10 @@ static void block(int *bsym, int *csym, int is_expr)
expect("switch");
nocode_wanted &= ~2;
next();
cr->v1 = cr->v2 = expr_const();
cr->v1 = cr->v2 = expr_const64();
if (gnu_ext && tok == TOK_DOTS) {
next();
cr->v2 = expr_const();
cr->v2 = expr_const64();
if (cr->v2 < cr->v1)
tcc_warning("empty case range");
}

View File

@ -606,6 +606,24 @@ enum test {
struct S_enum {
enum {E6 = 42, E7, E8} e:8;
};
enum ELong {
/* This is either 0 on L32 machines, or a large number
on L64 machines. We should be able to store this. */
EL_large = (unsigned long)0xf000 << 32,
};
enum { BIASU = -1U<<31 };
enum { BIASS = -1 << 31 };
static int getint(int i)
{
if (i)
return 0;
else
return (int)(-1U << 31);
}
void enum_test()
{
enum test b1;
@ -617,6 +635,16 @@ void enum_test()
E0, E1, E2, E3, E4, E5);
b1 = 1;
printf("b1=%d\n", b1);
printf("enum large: %ld\n", EL_large);
if (getint(0) == BIASU)
printf("enum unsigned: ok\n");
else
printf("enum unsigned: wrong\n");
if (getint(0) == BIASS)
printf("enum unsigned: ok\n");
else
printf("enum unsigned: wrong\n");
}
typedef int *my_ptr;
@ -1727,10 +1755,45 @@ void init_test(void)
printf("linit18= %d %d\n", linit18[0], linit18[1]);
}
void switch_uc(unsigned char uc)
{
switch (uc) {
case 0xfb ... 0xfe:
printf("ucsw:1\n");
break;
case 0xff:
printf("ucsw:2\n");
break;
case 0 ... 5:
printf("ucsw:3\n");
break;
default:
printf("ucsw: broken!\n");
}
}
void switch_sc(signed char sc)
{
switch (sc) {
case -5 ... -2:
printf("scsw:1\n");
break;
case -1:
printf("scsw:2\n");
break;
case 0 ... 5:
printf("scsw:3\n");
break;
default:
printf("scsw: broken!\n");
}
}
void switch_test()
{
int i;
unsigned long long ull;
long long ll;
for(i=0;i<15;i++) {
switch(i) {
@ -1753,6 +1816,60 @@ void switch_test()
}
}
printf("\n");
for (i = 1; i <= 5; i++) {
ull = (unsigned long long)i << 61;
switch (ull) {
case 1ULL << 61:
printf("ullsw:1\n");
break;
case 2ULL << 61:
printf("ullsw:2\n");
break;
case 3ULL << 61:
printf("ullsw:3\n");
break;
case 4ULL << 61:
printf("ullsw:4\n");
break;
case 5ULL << 61:
printf("ullsw:5\n");
break;
default:
printf("ullsw: broken!\n");
}
}
for (i = 1; i <= 5; i++) {
ll = (long long)i << 61;
switch (ll) {
case 1LL << 61:
printf("llsw:1\n");
break;
case 2LL << 61:
printf("llsw:2\n");
break;
case 3LL << 61:
printf("llsw:3\n");
break;
case 4LL << 61:
printf("llsw:4\n");
break;
case 5LL << 61:
printf("llsw:5\n");
break;
default:
printf("llsw: broken!\n");
}
}
for (i = -5; i <= 5; i++) {
switch_uc((unsigned char)i);
}
for (i = -5; i <= 5; i++) {
switch_sc ((signed char)i);
}
}
/* ISOC99 _Bool type */