From a5a50eaafeea0d3ca47bc8fb6baf983743470c60 Mon Sep 17 00:00:00 2001 From: Thomas Preud'homme Date: Sat, 15 Jan 2011 17:50:55 +0100 Subject: [PATCH] Implement C99 Variable Length Arrays Implement C99 Variable Length Arrays in tinycc: - Support VLA with multiple level (nested vla) - Update documentation with regards to VT_VLA - Add a testsuite in tcctest.c --- Changelog | 1 + TODO | 1 - tcc-doc.texi | 16 ++++- tcc.h | 2 + tccgen.c | 160 +++++++++++++++++++++++++++++++++++++++--------- tests/tcctest.c | 22 +++++++ 6 files changed, 169 insertions(+), 33 deletions(-) diff --git a/Changelog b/Changelog index fdccdbd..7a9f0c8 100644 --- a/Changelog +++ b/Changelog @@ -12,6 +12,7 @@ not released: - fix 32 to 64 bit casts, alignment and struct issues (Shinichiro Hamaji) - mimic all GNU -option forms supported by ld (Kirill Smelkov) - support indirect functions as externals (Thomas Preud'homme) +- Add support for C99 variable length arrays (Thomas Preud'homme) version 0.9.25: diff --git a/TODO b/TODO index 9d19153..74fe8a6 100644 --- a/TODO +++ b/TODO @@ -48,7 +48,6 @@ Missing features: - improve '-E' option. - atexit (Nigel Horne) - packed attribute -- C99: add variable size arrays (gcc 3.2 testsuite issue) - C99: add complex types (gcc 3.2 testsuite issue) - postfix compound literals (see 20010124-1.c) diff --git a/tcc-doc.texi b/tcc-doc.texi index fc65060..4ba4f46 100644 --- a/tcc-doc.texi +++ b/tcc-doc.texi @@ -929,7 +929,11 @@ be the best solution. #define VT_BTYPE 0x000f /* mask for basic type */ #define VT_UNSIGNED 0x0010 /* unsigned type */ #define VT_ARRAY 0x0020 /* array type (also has VT_PTR) */ +#define VT_VLA 0x10000 /* VLA type (also has VT_PTR and VT_ARRAY) */ #define VT_BITFIELD 0x0040 /* bitfield modifier */ +#define VT_CONSTANT 0x0800 /* const modifier */ +#define VT_VOLATILE 0x1000 /* volatile modifier */ +#define VT_SIGNED 0x2000 /* signed type */ #define VT_STRUCT_SHIFT 16 /* structure/enum name shift (16 bits left) */ @end example @@ -942,7 +946,8 @@ The @code{VT_UNSIGNED} flag can be set for chars, shorts, ints and long longs. Arrays are considered as pointers @code{VT_PTR} with the flag -@code{VT_ARRAY} set. +@code{VT_ARRAY} set. Variable length arrays are considered as special +arrays and therefore also have flag @code{VT_VLA} set. The @code{VT_BITFIELD} flag can be set for chars, shorts, ints and long longs. If it is set, then the bitfield position is stored from bits @@ -958,6 +963,10 @@ integer: #define VT_EXTERN 0x00000080 /* extern definition */ #define VT_STATIC 0x00000100 /* static variable */ #define VT_TYPEDEF 0x00000200 /* typedef definition */ +#define VT_INLINE 0x00000400 /* inline definition */ +#define VT_IMPORT 0x00004000 /* win32: extern data imported from dll */ +#define VT_EXPORT 0x00008000 /* win32: data exported from dll */ +#define VT_WEAK 0x00010000 /* win32: data exported from dll */ @end example @section Symbols @@ -969,7 +978,10 @@ contains @code{Sym} structures. an idenfier is also a token, so a string is never necessary to store it). @code{Sym.t} gives the type of the symbol. @code{Sym.r} is usually the register in which the corresponding variable is stored. @code{Sym.c} is -usually a constant associated to the symbol. +usually a constant associated to the symbol like its address for normal +symbols, and the number of entries for symbols representing arrays. +Variable length arrays use @code{Sym.r} instead, which is a pointer to +a @code{SValue} holding its runtime size. Four main symbol stacks are defined: diff --git a/tcc.h b/tcc.h index a3a9c78..22d79f0 100644 --- a/tcc.h +++ b/tcc.h @@ -224,6 +224,7 @@ typedef struct Sym { union { long c; /* associated number */ int *d; /* define token stream */ + SValue *s; /* associated stack value */ }; CType type; /* associated type */ union { @@ -616,6 +617,7 @@ struct TCCState { #define VT_BTYPE 0x000f /* mask for basic type */ #define VT_UNSIGNED 0x0010 /* unsigned type */ #define VT_ARRAY 0x0020 /* array type (also has VT_PTR) */ +#define VT_VLA 0x10000 /* VLA type (also has VT_PTR and VT_ARRAY) */ #define VT_BITFIELD 0x0040 /* bitfield modifier */ #define VT_CONSTANT 0x0800 /* const modifier */ #define VT_VOLATILE 0x1000 /* volatile modifier */ diff --git a/tccgen.c b/tccgen.c index 7144ccd..7fb7cfe 100644 --- a/tccgen.c +++ b/tccgen.c @@ -78,6 +78,7 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym, int case_re static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, int has_init, int v, int scope); static void expr_eq(void); static void unary_type(CType *type); +static void vla_runtime_type_size(CType *type, int *a); static int is_compatible_parameter_types(CType *type1, CType *type2); static void expr_type(CType *type); @@ -1455,6 +1456,12 @@ static int pointed_size(CType *type) return type_size(pointed_type(type), &align); } +static void vla_runtime_pointed_size(CType *type) +{ + int align; + vla_runtime_type_size(pointed_type(type), &align); +} + static inline int is_null_pointer(SValue *p) { if ((p->r & (VT_VALMASK | VT_LVAL | VT_SYM)) != VT_CONST) @@ -1543,11 +1550,20 @@ ST_FUNC void gen_op(int op) } /* if both pointers, then it must be the '-' op */ if (bt1 == VT_PTR && bt2 == VT_PTR) { + int ptr1_is_vla; + + ptr1_is_vla = 0; if (op != '-') error("cannot use pointers here"); check_comparison_pointer_types(vtop - 1, vtop, op); /* XXX: check that types are compatible */ - u = pointed_size(&vtop[-1].type); + if (vtop[-1].type.t & VT_VLA) { + vla_runtime_pointed_size(&vtop[-1].type); + vrott(3); + ptr1_is_vla = 1; + } else { + u = pointed_size(&vtop[-1].type); + } gen_opic(op); /* set to integer type */ #ifdef TCC_TARGET_X86_64 @@ -1555,7 +1571,10 @@ ST_FUNC void gen_op(int op) #else vtop->type.t = VT_INT; #endif - vpushi(u); + if (ptr1_is_vla) + vswap(); + else + vpushi(u); gen_op(TOK_PDIV); } else { /* exactly one pointer : must be '+' or '-'. */ @@ -1567,16 +1586,20 @@ ST_FUNC void gen_op(int op) swap(&t1, &t2); } type1 = vtop[-1].type; - type1.t &= ~VT_ARRAY; - u = pointed_size(&vtop[-1].type); - if (u < 0) - error("unknown array element size"); + type1.t &= ~(VT_ARRAY|VT_VLA); + if (vtop[-1].type.t & VT_VLA) + vla_runtime_pointed_size(&vtop[-1].type); + else { + u = pointed_size(&vtop[-1].type); + if (u < 0) + error("unknown array element size"); #ifdef TCC_TARGET_X86_64 - vpushll(u); + vpushll(u); #else - /* XXX: cast to int ? (long long case) */ - vpushi(u); + /* XXX: cast to int ? (long long case) */ + vpushi(u); #endif + } gen_op('*'); #ifdef CONFIG_TCC_BCHECK /* if evaluating constant expression, no code should be @@ -1932,7 +1955,7 @@ static void gen_cast(CType *type) vtop->type = *type; } -/* return type size. Put alignment at 'a' */ +/* return type size as known at compile time. Put alignment at 'a' */ ST_FUNC int type_size(CType *type, int *a) { Sym *s; @@ -1945,7 +1968,10 @@ ST_FUNC int type_size(CType *type, int *a) *a = s->r; return s->c; } else if (bt == VT_PTR) { - if (type->t & VT_ARRAY) { + if (type->t & VT_VLA) { + *a = 1; + return 0; + } else if (type->t & VT_ARRAY) { int ts; s = type->ref; @@ -1992,6 +2018,30 @@ ST_FUNC int type_size(CType *type, int *a) } } +/* push type size as known at runtime time on top of value stack. Put + alignment at 'a' */ +ST_FUNC void vla_runtime_type_size(CType *type, int *a) +{ + if (type->t & VT_VLA) { + Sym *s; + + s = type->ref; + vla_runtime_type_size(&s->type, a); + vpushv(s->s); + if ((vtop->r & (VT_SYM|VT_LVAL|VT_VALMASK)) != VT_CONST) { + gv_dup(); + vswap(); + vpop(); + } + gen_op('*'); + } else { + int size; + + size = type_size(type, a); + vpushi(size); + } +} + /* return the pointed type of t */ static inline CType *pointed_type(CType *type) { @@ -3103,25 +3153,43 @@ static void post_type(CType *type, AttributeDef *ad) type->t = t1 | VT_FUNC; type->ref = s; } else if (tok == '[') { + SValue *last_vtop = NULL; + /* array definition */ next(); if (tok == TOK_RESTRICT1) next(); n = -1; if (tok != ']') { - n = expr_const(); - if (n < 0) - error("invalid array size"); + gexpr(); + if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { + n = vtop->c.i; + last_vtop = vtop; + if (n < 0) + error("invalid array size"); + } else { + if (!is_integer_btype(vtop->type.t & VT_BTYPE)) + error("size of variable length array should be an integer"); + type->t |= VT_VLA; + last_vtop = vtop; + } } skip(']'); /* parse next post type */ - t1 = type->t & VT_STORAGE; - type->t &= ~VT_STORAGE; + t1 = type->t & (VT_STORAGE|VT_VLA); + type->t &= ~(VT_STORAGE|VT_VLA); post_type(type, ad); + t1 |= type->t & VT_VLA; - /* we push a anonymous symbol which will contain the array + /* we push an anonymous symbol which will contain the array element type */ s = sym_push(SYM_FIELD, type, 0, n); + if (t1 & VT_VLA) { + s->s = last_vtop; // That's ok, we don't need n with VLA + } else { + if (n >= 0) + vpop(); + } type->t = t1 | VT_ARRAY | VT_PTR; type->ref = s; } @@ -3490,11 +3558,18 @@ ST_FUNC void unary(void) next(); in_sizeof++; unary_type(&type); // Perform a in_sizeof = 0; - size = type_size(&type, &align); + if ((t == TOK_SIZEOF) && (type.t & VT_VLA)) { + vla_runtime_type_size(&type, &align); + size = 0; + } else { + size = type_size(&type, &align); + } if (t == TOK_SIZEOF) { - if (size < 0) - error("sizeof applied to an incomplete type"); - vpushi(size); + if (!(type.t & VT_VLA)) { + if (size < 0) + error("sizeof applied to an incomplete type"); + vpushi(size); + } } else { vpushi(align); } @@ -3653,7 +3728,10 @@ ST_FUNC void unary(void) } else { r = s->r; } - vset(&s->type, r, s->c); + if (s->type.t & VT_VLA) + vpushv(s->s); + else + vset(&s->type, r, s->c); /* if forward reference, we must point to s */ if (vtop->r & VT_SYM) { vtop->sym = s; @@ -4782,7 +4860,18 @@ static void decl_initializer(CType *type, Section *sec, unsigned long c, Sym *s, *f; CType *t1; - if (type->t & VT_ARRAY) { + if (type->t & VT_VLA) { + int a; + CValue retcval; + + vpush_global_sym(&func_old_type, TOK_alloca); + vla_runtime_type_size(type, &a); + gfunc_call(1); + + /* return value */ + retcval.i = 0; + vsetc(type, REG_IRET, &retcval); + } else if (type->t & VT_ARRAY) { s = type->ref; n = s->c; array_length = 0; @@ -5013,6 +5102,7 @@ static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, ParseState saved_parse_state = {0}; TokenString init_str; Section *sec; + Sym *vla = NULL; size = type_size(type, &align); /* If unknown size, we must evaluate it before @@ -5078,8 +5168,12 @@ static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, if ((r & VT_VALMASK) == VT_LOCAL) { sec = NULL; #ifdef CONFIG_TCC_BCHECK - if (tcc_state->do_bounds_check && (type->t & VT_ARRAY)) - loc--; + if (tcc_state->do_bounds_check && (type->t & VT_ARRAY)) { + if (type->t & VT_VLA) + warning("Array bound check don't work for VLA"); + else + loc--; + } #endif loc = (loc - size) & -align; addr = loc; @@ -5087,7 +5181,8 @@ static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, /* handles bounds */ /* XXX: currently, since we do only one pass, we cannot track '&' operators, so we add only arrays */ - if (tcc_state->do_bounds_check && (type->t & VT_ARRAY)) { + if (tcc_state->do_bounds_check && (type->t & VT_ARRAY) && + !(type->t & VT_VLA)) { unsigned long *bounds_ptr; /* add padding between regions */ loc--; @@ -5099,7 +5194,7 @@ static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, #endif if (v) { /* local variable */ - sym_push(v, type, r, addr); + vla = sym_push(v, type, r, addr); } else { /* push local reference */ vset(type, r, addr); @@ -5214,8 +5309,10 @@ static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, } #endif } - if (has_init) { + if (has_init || (type->t & VT_VLA)) { decl_initializer(type, sec, addr, 1, 0); + if (type->t & VT_VLA) + vla->s = vtop; /* restore parse state if needed */ if (init_str.str) { tok_str_free(init_str.str); @@ -5584,9 +5681,12 @@ ST_FUNC void decl(int l) if (!(type.t & VT_ARRAY)) r |= lvalue_type(type.t); has_init = (tok == '='); + if (has_init && (type.t & VT_VLA)) + error("Variable length array cannot be initialized"); if ((btype.t & VT_EXTERN) || - ((type.t & VT_ARRAY) && (type.t & VT_STATIC) && - !has_init && l == VT_CONST && type.ref->c < 0)) { + ((type.t & VT_ARRAY) && (!(type.t & VT_VLA)) && + (type.t & VT_STATIC) && !has_init && l == VT_CONST && + type.ref->c < 0)) { /* external variable */ /* NOTE: as GCC, uninitialized global static arrays of null size are considered as diff --git a/tests/tcctest.c b/tests/tcctest.c index 87e7ad1..499a071 100644 --- a/tests/tcctest.c +++ b/tests/tcctest.c @@ -77,6 +77,7 @@ void whitespace_test(void); void relocation_test(void); void old_style_function(void); void alloca_test(void); +void c99_vla_test(int size1, int size2); void sizeof_test(void); void typeof_test(void); void local_label_test(void); @@ -563,6 +564,7 @@ int main(int argc, char **argv) relocation_test(); old_style_function(); alloca_test(); + c99_vla_test(5, 2); sizeof_test(); typeof_test(); statement_expr_test(); @@ -2018,6 +2020,26 @@ void alloca_test() #endif } +void c99_vla_test(int size1, int size2) +{ +#if defined __i386__ || defined __x86_64__ + int tab1[size1 * size2][2], tab2[10][2]; + void *tab1_ptr, *tab2_ptr; + + printf("Test C99 VLA 1 (sizeof): "); + printf("%s\n", (sizeof tab1 == size1 * size2 * 2 * sizeof(int)) ? "PASSED" : "FAILED"); + tab1_ptr = tab1; + tab2_ptr = tab2; + printf("Test C99 VLA 2 (ptrs substract): "); + printf("%s\n", (tab2 - tab1 == (tab2_ptr - tab1_ptr) / (sizeof(int) * 2)) ? "PASSED" : "FAILED"); + printf("Test C99 VLA 3 (ptr add): "); + printf("%s\n", &tab1[5][1] == (tab1_ptr + (5 * 2 + 1) * sizeof(int)) ? "PASSED" : "FAILED"); + printf("Test C99 VLA 4 (ptr access): "); + tab1[size1][1] = 42; + printf("%s\n", (*((int *) (tab1_ptr + (size1 * 2 + 1) * sizeof(int))) == 42) ? "PASSED" : "FAILED"); +#endif +} + void sizeof_test(void) { int a;