work on local extern declarations

Example:
    int a = 1;
    void f(void)
    {
        int a = 2;
        {
             extern int a; // = 1 !!
             ....

To get this (more) correctly there is a new function to copy
syms between local to global stacks.

Also, this patch changes the meaning of VT_EXTERN back
to the simpler and IMO more useful notion of
    DECLARED but not (yet) DEFINED.
and that for both variables and functions.  That is, VT_EXTERN
in tcc doesn't have to do with the keyword 'extern' necessarily.

Also this patch does allow
    int x[];
as alias for
    extern int x[];
(as do gcc and msvc)
This commit is contained in:
grischka 2019-06-22 04:00:52 +02:00
parent cbbba01b46
commit 8569048031
4 changed files with 214 additions and 86 deletions

View file

@ -38,7 +38,7 @@ static Sym* asm_new_label1(TCCState *s1, int label, int is_local, int sh_num, in
static Sym *asm_label_find(int v) static Sym *asm_label_find(int v)
{ {
Sym *sym = sym_find(v); Sym *sym = sym_find(v);
while (sym && sym->sym_scope) while (sym && sym->sym_scope && !(sym->type.t & VT_STATIC))
sym = sym->prev_tok; sym = sym->prev_tok;
return sym; return sym;
} }

181
tccgen.c
View file

@ -263,6 +263,7 @@ ST_FUNC int tccgen_compile(TCCState *s1)
section_sym = 0; section_sym = 0;
const_wanted = 0; const_wanted = 0;
nocode_wanted = 0x80000000; nocode_wanted = 0x80000000;
local_scope = 0;
/* define some often used types */ /* define some often used types */
int_type.t = VT_INT; int_type.t = VT_INT;
@ -325,8 +326,7 @@ ST_FUNC void update_storage(Sym *sym)
esym->st_other = (esym->st_other & ~ELFW(ST_VISIBILITY)(-1)) esym->st_other = (esym->st_other & ~ELFW(ST_VISIBILITY)(-1))
| sym->a.visibility; | sym->a.visibility;
if ((sym->type.t & VT_STATIC) if (sym->type.t & (VT_STATIC | VT_INLINE))
|| (sym->type.t & (VT_EXTERN | VT_INLINE)) == VT_INLINE)
sym_bind = STB_LOCAL; sym_bind = STB_LOCAL;
else if (sym->a.weak) else if (sym->a.weak)
sym_bind = STB_WEAK; sym_bind = STB_WEAK;
@ -408,7 +408,7 @@ ST_FUNC void put_extern_sym2(Sym *sym, int sh_num,
} else { } else {
sym_type = STT_OBJECT; sym_type = STT_OBJECT;
} }
if ((t & VT_STATIC) || (t & (VT_EXTERN | VT_INLINE)) == VT_INLINE) if (t & (VT_STATIC | VT_INLINE))
sym_bind = STB_LOCAL; sym_bind = STB_LOCAL;
else else
sym_bind = STB_GLOBAL; sym_bind = STB_GLOBAL;
@ -927,8 +927,7 @@ static void merge_attr(AttributeDef *ad, AttributeDef *ad1)
/* Merge some type attributes. */ /* Merge some type attributes. */
static void patch_type(Sym *sym, CType *type) static void patch_type(Sym *sym, CType *type)
{ {
if ((!(type->t & VT_EXTERN) || IS_ENUM_VAL(sym->type.t)) if (!(type->t & VT_EXTERN) || IS_ENUM_VAL(sym->type.t)) {
&& (type->t & VT_BTYPE) != VT_FUNC) {
if (!(sym->type.t & VT_EXTERN)) if (!(sym->type.t & VT_EXTERN))
tcc_error("redefinition of '%s'", get_tok_str(sym->v, NULL)); tcc_error("redefinition of '%s'", get_tok_str(sym->v, NULL));
sym->type.t &= ~VT_EXTERN; sym->type.t &= ~VT_EXTERN;
@ -955,29 +954,30 @@ static void patch_type(Sym *sym, CType *type)
tcc_warning("static storage ignored for redefinition of '%s'", tcc_warning("static storage ignored for redefinition of '%s'",
get_tok_str(sym->v, NULL)); get_tok_str(sym->v, NULL));
/* Force external definition if unequal inline specifier /* set 'inline' if both agree or if one has static */
or an explicit extern one. */ if ((type->t | sym->type.t) & VT_INLINE) {
if ((sym->type.t | type->t) & VT_STATIC) { if (!((type->t ^ sym->type.t) & VT_INLINE)
type->t |= sym->type.t & VT_INLINE; || ((type->t | sym->type.t) & VT_STATIC))
sym->type.t |= type->t & VT_INLINE; static_proto |= VT_INLINE;
} else if (((type->t & VT_INLINE) != (sym->type.t & VT_INLINE)
|| (type->t | sym->type.t) & VT_EXTERN)
&& !static_proto) {
type->t &= ~VT_INLINE;
sym->type.t &= ~VT_INLINE;
} }
if (0 == (type->t & VT_EXTERN)) { if (0 == (type->t & VT_EXTERN)) {
/* put complete type, use static from prototype, but don't /* put complete type, use static from prototype */
overwrite type.ref, it might contain parameter names */ sym->type.t = (type->t & ~(VT_STATIC|VT_INLINE)) | static_proto;
sym->type.t = (type->t & ~VT_STATIC) | static_proto; sym->type.ref = type->ref;
} else {
sym->type.t &= ~VT_INLINE | static_proto;
} }
if (sym->type.ref->f.func_type == FUNC_OLD
&& type->ref->f.func_type != FUNC_OLD) {
sym->type.ref = type->ref;
}
} else { } else {
if ((sym->type.t & VT_ARRAY) && type->ref->c >= 0) { if ((sym->type.t & VT_ARRAY) && type->ref->c >= 0) {
/* set array size if it was omitted in extern declaration */ /* set array size if it was omitted in extern declaration */
if (sym->type.ref->c < 0)
sym->type.ref->c = type->ref->c; sym->type.ref->c = type->ref->c;
else if (sym->type.ref->c != type->ref->c)
tcc_error("conflicting type for '%s'", get_tok_str(sym->v, NULL));
} }
if ((type->t ^ sym->type.t) & VT_STATIC) if ((type->t ^ sym->type.t) & VT_STATIC)
tcc_warning("storage mismatch for redefinition of '%s'", tcc_warning("storage mismatch for redefinition of '%s'",
@ -985,7 +985,6 @@ static void patch_type(Sym *sym, CType *type)
} }
} }
/* Merge some storage attributes. */ /* Merge some storage attributes. */
static void patch_storage(Sym *sym, AttributeDef *ad, CType *type) static void patch_storage(Sym *sym, AttributeDef *ad, CType *type)
{ {
@ -1003,29 +1002,55 @@ static void patch_storage(Sym *sym, AttributeDef *ad, CType *type)
update_storage(sym); update_storage(sym);
} }
/* copy sym to other stack */
static Sym *sym_copy(Sym *s0, Sym **ps)
{
Sym *s;
s = sym_malloc(), *s = *s0;
s->prev = *ps, *ps = s;
if (s->v < SYM_FIRST_ANOM) {
ps = &table_ident[s->v - TOK_IDENT]->sym_identifier;
s->prev_tok = *ps, *ps = s;
}
return s;
}
/* copy a list of syms */
static void sym_copy_ref(Sym *s0, Sym **ps)
{
Sym *s, **sp = &s0->type.ref;
for (s = *sp, *sp = NULL; s; s = s->next)
sp = &(*sp = sym_copy(s, ps))->next;
}
/* define a new external reference to a symbol 'v' */ /* define a new external reference to a symbol 'v' */
static Sym *external_sym(int v, CType *type, int r, AttributeDef *ad) static Sym *external_sym(int v, CType *type, int r, AttributeDef *ad)
{ {
Sym *s; Sym *s; int bt;
/* look for global symbol */
s = sym_find(v); s = sym_find(v);
if (!s || (!IS_ASM_SYM(s) && !(s->type.t & VT_EXTERN) while (s && s->sym_scope)
&& (!(type->t & VT_EXTERN) || s->sym_scope) s = s->prev_tok;
&& (s->type.t & VT_BTYPE) != VT_FUNC)) {
if (s && !is_compatible_types(&s->type, type)) if (!s) {
tcc_error("conflicting types for '%s'", get_tok_str(s->v, NULL));
/* push forward reference */ /* push forward reference */
s = sym_push(v, type, r | VT_CONST | VT_SYM, 0); s = global_identifier_push(v, type->t, 0);
s->r |= r;
s->a = ad->a; s->a = ad->a;
s->asm_label = ad->asm_label; s->asm_label = ad->asm_label;
s->sym_scope = 0;
} else {
if (s->type.ref == func_old_type.ref) {
s->type.ref = type->ref; s->type.ref = type->ref;
s->r = r | VT_CONST | VT_SYM; bt = s->type.t & (VT_BTYPE|VT_ARRAY);
s->type.t |= VT_EXTERN; /* copy type to the global stack also */
} if (local_scope && (bt == VT_FUNC || (bt & VT_ARRAY)))
sym_copy_ref(s, &global_stack);
} else {
patch_storage(s, ad, type); patch_storage(s, ad, type);
bt = s->type.t & VT_BTYPE;
} }
/* push variables to local scope if any */
if (local_stack && bt != VT_FUNC)
s = sym_copy(s, &local_stack);
return s; return s;
} }
@ -2880,27 +2905,29 @@ static int is_compatible_func(CType *type1, CType *type2)
s1 = type1->ref; s1 = type1->ref;
s2 = type2->ref; s2 = type2->ref;
if (!is_compatible_types(&s1->type, &s2->type))
return 0;
/* check func_call */
if (s1->f.func_call != s2->f.func_call) if (s1->f.func_call != s2->f.func_call)
return 0; return 0;
/* XXX: not complete */ if (s1->f.func_type != s2->f.func_type
if (s1->f.func_type == FUNC_OLD || s2->f.func_type == FUNC_OLD) && s1->f.func_type != FUNC_OLD
&& s2->f.func_type != FUNC_OLD)
return 0;
/* we should check the function return type for FUNC_OLD too
but that causes problems with the internally used support
functions such as TOK_memmove */
if (s1->f.func_type == FUNC_OLD && !s1->next)
return 1; return 1;
if (s1->f.func_type != s2->f.func_type) if (s2->f.func_type == FUNC_OLD && !s2->next)
return 0; return 1;
while (s1 != NULL) { for (;;) {
if (s2 == NULL)
return 0;
if (!is_compatible_unqualified_types(&s1->type, &s2->type)) if (!is_compatible_unqualified_types(&s1->type, &s2->type))
return 0; return 0;
s1 = s1->next; s1 = s1->next;
s2 = s2->next; s2 = s2->next;
} if (!s1)
if (s2) return !s2;
if (!s2)
return 0; return 0;
return 1; }
} }
/* return true if type1 and type2 are the same. If unqualified is /* return true if type1 and type2 are the same. If unqualified is
@ -2926,15 +2953,19 @@ static int compare_types(CType *type1, CType *type2, int unqualified)
/* XXX: bitfields ? */ /* XXX: bitfields ? */
if (t1 != t2) if (t1 != t2)
return 0; return 0;
if ((t1 & VT_ARRAY)
&& !(type1->ref->c < 0
|| type2->ref->c < 0
|| type1->ref->c == type2->ref->c))
return 0;
/* test more complicated cases */ /* test more complicated cases */
bt1 = t1 & (VT_BTYPE | VT_ARRAY); bt1 = t1 & VT_BTYPE;
if (bt1 == VT_PTR) { if (bt1 == VT_PTR) {
type1 = pointed_type(type1); type1 = pointed_type(type1);
type2 = pointed_type(type2); type2 = pointed_type(type2);
return is_compatible_types(type1, type2); return is_compatible_types(type1, type2);
} else if (bt1 & VT_ARRAY) {
return type1->ref->c < 0 || type2->ref->c < 0
|| type1->ref->c == type2->ref->c;
} else if (bt1 == VT_STRUCT) { } else if (bt1 == VT_STRUCT) {
return (type1->ref == type2->ref); return (type1->ref == type2->ref);
} else if (bt1 == VT_FUNC) { } else if (bt1 == VT_FUNC) {
@ -7285,9 +7316,6 @@ static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r,
sym = sym_push(v, type, r | VT_SYM, 0); sym = sym_push(v, type, r | VT_SYM, 0);
patch_storage(sym, ad, NULL); patch_storage(sym, ad, NULL);
} }
/* Local statics have a scope until now (for
warnings), remove it here. */
sym->sym_scope = 0;
/* update symbol definition */ /* update symbol definition */
put_extern_sym(sym, sec, addr, size); put_extern_sym(sym, sec, addr, size);
} else { } else {
@ -7431,7 +7459,7 @@ static void gen_inline_functions(TCCState *s)
for (i = 0; i < s->nb_inline_fns; ++i) { for (i = 0; i < s->nb_inline_fns; ++i) {
fn = s->inline_fns[i]; fn = s->inline_fns[i];
sym = fn->sym; sym = fn->sym;
if (sym && (sym->c || !(sym->type.t & (VT_INLINE | VT_STATIC)) )) { if (sym && (sym->c || !(sym->type.t & VT_INLINE))) {
/* the function was used or forced (and then not internal): /* the function was used or forced (and then not internal):
generate its code and convert it to a normal function */ generate its code and convert it to a normal function */
fn->sym = NULL; fn->sym = NULL;
@ -7544,22 +7572,26 @@ static int decl0(int l, int is_for_loop_init, Sym *func_sym)
} }
#endif #endif
if ((type.t & VT_BTYPE) == VT_FUNC) { if ((type.t & VT_BTYPE) == VT_FUNC) {
if ((type.t & VT_STATIC) && (l == VT_LOCAL)) {
tcc_error("function without file scope cannot be static");
}
/* if old style function prototype, we accept a /* if old style function prototype, we accept a
declaration list */ declaration list */
sym = type.ref; sym = type.ref;
if (sym->f.func_type == FUNC_OLD && l == VT_CONST) if (sym->f.func_type == FUNC_OLD && l == VT_CONST)
decl0(VT_CMP, 0, sym); decl0(VT_CMP, 0, sym);
/* always compile 'extern inline' */
if (type.t & VT_EXTERN)
type.t &= ~VT_INLINE;
} }
if (gnu_ext && (tok == TOK_ASM1 || tok == TOK_ASM2 || tok == TOK_ASM3)) { if (gnu_ext && (tok == TOK_ASM1 || tok == TOK_ASM2 || tok == TOK_ASM3)) {
ad.asm_label = asm_label_instr(); ad.asm_label = asm_label_instr();
/* parse one last attribute list, after asm label */ /* parse one last attribute list, after asm label */
parse_attribute(&ad); parse_attribute(&ad);
#if 0
/* gcc does not allow __asm__("label") with function definition,
but why not ... */
if (tok == '{') if (tok == '{')
expect(";"); expect(";");
#endif
} }
#ifdef TCC_TARGET_PE #ifdef TCC_TARGET_PE
@ -7591,18 +7623,12 @@ static int decl0(int l, int is_for_loop_init, Sym *func_sym)
} }
/* put function symbol */ /* put function symbol */
type.t &= ~VT_EXTERN;
sym = external_sym(v, &type, 0, &ad); sym = external_sym(v, &type, 0, &ad);
/* This is the def, so overwrite any other parameter names
we got from prototypes. */
sym->type.ref = type.ref;
if (sym->c && elfsym(sym)->st_shndx != SHN_UNDEF
&& !(elfsym(sym)->st_other & ST_ASM_SET))
tcc_error("redefinition of '%s'", get_tok_str(sym->v, NULL));
/* static inline functions are just recorded as a kind /* static inline functions are just recorded as a kind
of macro. Their code will be emitted at the end of of macro. Their code will be emitted at the end of
the compilation unit only if they are used */ the compilation unit only if they are used */
if ((sym->type.t & (VT_INLINE | VT_EXTERN)) == VT_INLINE) { if (sym->type.t & VT_INLINE) {
struct InlineFunc *fn; struct InlineFunc *fn;
const char *filename; const char *filename;
@ -7669,15 +7695,14 @@ found:
has_init = (tok == '='); has_init = (tok == '=');
if (has_init && (type.t & VT_VLA)) if (has_init && (type.t & VT_VLA))
tcc_error("variable length array cannot be initialized"); tcc_error("variable length array cannot be initialized");
if (((type.t & VT_EXTERN) && (!has_init || l != VT_CONST)) || if (((type.t & VT_EXTERN) && (!has_init || l != VT_CONST))
((type.t & VT_BTYPE) == VT_FUNC) || || (type.t & VT_BTYPE) == VT_FUNC
((type.t & VT_ARRAY) && (type.t & VT_STATIC) && /* as with GCC, uninitialized global arrays with no size
!has_init && l == VT_CONST && type.ref->c < 0)) { are considered extern: */
|| ((type.t & VT_ARRAY) && !has_init
&& l == VT_CONST && type.ref->c < 0)
) {
/* external variable or function */ /* external variable or function */
/* NOTE: as GCC, uninitialized global static
arrays of null size are considered as
extern */
if ((type.t & VT_BTYPE) != VT_FUNC)
type.t |= VT_EXTERN; type.t |= VT_EXTERN;
sym = external_sym(v, &type, r, &ad); sym = external_sym(v, &type, r, &ad);
if (ad.alias_target) { if (ad.alias_target) {
@ -7687,9 +7712,6 @@ found:
esym = elfsym(alias_target); esym = elfsym(alias_target);
if (!esym) if (!esym)
tcc_error("unsupported forward __alias__ attribute"); tcc_error("unsupported forward __alias__ attribute");
/* Local statics have a scope until now (for
warnings), remove it here. */
sym->sym_scope = 0;
put_extern_sym2(sym, esym->st_shndx, esym->st_value, esym->st_size, 0); put_extern_sym2(sym, esym->st_shndx, esym->st_value, esym->st_size, 0);
} }
} else { } else {
@ -7699,8 +7721,7 @@ found:
r |= l; r |= l;
if (has_init) if (has_init)
next(); next();
else if (l == VT_CONST else if (l == VT_CONST)
&& (type.t & VT_BTYPE) != VT_FUNC)
/* uninitialized global variables may be overridden */ /* uninitialized global variables may be overridden */
type.t |= VT_EXTERN; type.t |= VT_EXTERN;
decl_initializer_alloc(&type, &ad, r, has_init, v, l); decl_initializer_alloc(&type, &ad, r, has_init, v, l);

View file

@ -196,6 +196,82 @@ void * _Alignas(16) p1;
const f t[3]; const f t[3];
#elif defined test_incomplete_array_array #elif defined test_incomplete_array_array
int t[][3]; int t[][3]; // gr: not an error, see below
/******************************************************************/
#elif defined test_extern_array
int iii[] = { 1,2,3 };
extern int iii[];
int x[];
int x[2];
int x[];
int x[2];
int x[];
extern int x[2];
extern int x[];
int x[3];
/******************************************************************/
#elif defined test_func_1 \
|| defined test_func_2 \
|| defined test_func_3 \
|| defined test_func_4 \
|| defined test_func_5
#if defined test_func_1
int hello(int);
#elif defined test_func_4
static int hello(int);
#endif
int main () {
#if defined test_func_5
static
#endif
int hello(int);
hello(123);
return 0;
}
int printf(const char*, ...);
#if defined test_func_3
static int hello(int a)
#elif defined test_func_5
int hello(int a, int b)
#else
int hello(int a)
#endif
{ printf("%s: a = %d\n", __FUNCTION__, a); return 0; }
/******************************************************************/
#elif defined test_var_1 \
|| defined test_var_2 \
|| defined test_var_3
#define P(n,v) printf("%-5s: %d ; %d\n", __FUNCTION__, n, v)
#if defined test_var_1
int xxx[];
#endif
int bar();
int printf(const char*, ...);
int main ()
{
#if !defined test_var_3
int xxx = 2;
#endif
{
extern int xxx[
#if defined test_var_3
2
#endif
];
P(1, xxx[0]);
xxx[0] += 2;
}
#if !defined test_var_3
P(2, xxx);
#endif
bar(123);
return 0;
}
int xxx[1] = {1};
int bar() { P(3, xxx[0]); return 0; }
/******************************************************************/
#endif #endif

View file

@ -73,7 +73,7 @@
60_errors_and_warnings.c:153: error: ',' expected (got "a") 60_errors_and_warnings.c:153: error: ',' expected (got "a")
[test_conflicting_types] [test_conflicting_types]
60_errors_and_warnings.c:159: error: conflicting types for 'i' 60_errors_and_warnings.c:159: error: incompatible types for redefinition of 'i'
[test_nested_types] [test_nested_types]
60_errors_and_warnings.c:166: error: struct/union/enum already defined 60_errors_and_warnings.c:166: error: struct/union/enum already defined
@ -100,4 +100,35 @@
60_errors_and_warnings.c:196: error: declaration of an array of functions 60_errors_and_warnings.c:196: error: declaration of an array of functions
[test_incomplete_array_array] [test_incomplete_array_array]
60_errors_and_warnings.c:199: error: unknown type size
[test_extern_array]
60_errors_and_warnings.c:212: error: incompatible types for redefinition of 'x'
[test_func_1]
hello: a = 123
[test_func_2]
hello: a = 123
[test_func_3]
60_errors_and_warnings.c:241: warning: static storage ignored for redefinition of 'hello'
hello: a = 123
[test_func_4]
hello: a = 123
[test_func_5]
60_errors_and_warnings.c:241: error: incompatible types for redefinition of 'hello'
[test_var_1]
main : 1 ; 1
main : 2 ; 2
bar : 3 ; 3
[test_var_2]
main : 1 ; 1
main : 2 ; 2
bar : 3 ; 3
[test_var_3]
60_errors_and_warnings.c:273: error: incompatible types for redefinition of 'xxx'