diff mbox

Multiple translation unit regression

Message ID 4A5131F4.7070007@ramsay1.demon.co.uk (mailing list archive)
State Rejected, archived
Headers show

Commit Message

Ramsay Jones July 5, 2009, 11:06 p.m. UTC
Hi Chris,

The recent patches from Linus caught my eye, since I have my own
versions of two of them (the array restrict and transparent_union
patches) in my sparse repo. (I suspect for the same reason; try to
reduce the number of sparse warnings on git. I also have a quick
hack to sorta-kinda implement transparent_union on glibc/Linux;
new-lib/cygwin doesn't need it. I did manage to get to zero
warnings at one point).

So, I decided to fetch from your repo, in order to upgrade to the
latest sparse (I was still using Josh's repo, commit e3bff51f, as
the basis of my version). Having done so, I noticed a regression
when using sparse on libgit2.

<offtopic for="Chris, Junio">
I note that both sparse and git have only one "serious" sparse
error; but it is effectively the same one. If you run sparse
over itself you will find:

    pre-process.c:609:25: error: bad constant expression

and for git (on platforms for which THREADED_DELTA_SEARCH is
defined):

    builtin-pack-objects.c:1606:32: error: bad constant expression

These errors relate to using the "dynamic local arrays" gcc
extension (aka C99 VLAs); viz:

pre-process.c:609 in function expand():
	struct arg args[nargs];

builtin-pack-objects.c:1606 in function ll_find_deltas():
	struct thread_params p[delta_search_threads];

So, whether this is actually a problem, depends on the project
policy regarding this extension...
</offtopic>

In libgit2, the sparse Makefile target includes all source files
(via the SRC_C macro) in a single invocation of cgcc, thus:

sparse:
	cgcc -no-compile $(ALL_CFLAGS) $(SPARSE_FLAGS) $(SRC_C)

this results in the follwing warnings (on cygwin):

.../byteorder.h:39:1: warning: multiple definitions for function '__ntohl'
.../byteorder.h:39:1:  the previous one is here
.../byteorder.h:56:1: warning: multiple definitions for function '__ntohs'
.../byteorder.h:56:1:  the previous one is here

repeated 10 times. (Hint: it is significant that the number of source
files is 11 [-1 = 10]). It is much much worse on Linux, where the glibc
header files have many more extern inline function declarations.

Note that the previous definition of __ntohl is at, er... ;-)

However, if you run cgcc on each file separately, then no warnings
are issued!

To make things a little clearer, try this:

$ cat -n test.c
     1	
     2	extern __inline__ int f(int);
     3	
     4	extern __inline__ int
     5	f(int x)
     6	{
     7		return x;
     8	}
     9	
$ ./sparse test.c
$ ./sparse test.c test.c
test.c:5:1: warning: multiple definitions for function 'f'
test.c:5:1:  the previous one is here
$ cp test.c test-again.c
$ ./sparse test.c test-again.c
test-again.c:5:1: warning: multiple definitions for function 'f'
test.c:5:1:  the previous one is here
$ 

So, the previous definition was in the previous translation unit!

I then used "git bisect" to try and find the culprit, and it fingered:

    commit 0ed9c1290e9fd37e6a320d16c621beba202d422e
    Author: Al Viro <viro@ZenIV.linux.org.uk>
    Date:   Mon Feb 2 07:30:19 2009 +0000

        fun with declarations and definitions
    
        ...

The third hunk of the diff to parse.c actually triggers the problem,
but is not the real cause of the regression.

I think the problem is mainly caused by bind_symbol() binding the
global_scope to some symbols. Indeed, the global_scope/file_scope
seems to be a bit confused and confusing. Note that the global_scope
is not changed at all once intialised. In particular, each translation
unit keeps adding to the, one and only, global_scope; which is
effectively the same as the builtin_scope!

Hmm, the following diff shows a quick fix:

--->8---
--->8---

But this is not the correct fix. In fact it may have broken c2xml
and ctags. I haven't checked, but look at the main() code in c2xml.c
and ctags.c; again the semantics of global_scope vs. file_scope
seems confused and confusing. At least to me. ;-)

Note the comment in the first hunk above. I suspect that the
global_scope was intended to be used to check the semantics of
local extern declarations, which are effectively hoisted to global
scope, in order the check the type of the re-declarations (and
maybe re-definitions). (but it would be a separate symbol table,
used only for the semantic checks).

Hmm, you would also need to compose the two types, check the
resulting linkage... Take a look at this example:

$ cat extern-test.c

int f(void) { extern float g(int); return 0; }
int g(void) { return -1; } /* ERROR, conflicting type for g() */
int h(void) { extern double g(void); return 0; }  /* ditto */

int x;  /* the extern decl. below conflicts with this x, not param */
int k(int x)
{
	{ extern float x; x=1; }  /* ERROR, conflicting type for x */
	return x;  /* this is the parameter x */
}

static int y; /* y has internal linkage */
extern int y; /* OK, y still has internal linkage */
static int y; /* OK, y still has internal linkage */
int y;        /* ERROR, y has internal linkage */

extern int z; /* z has external linkage */
extern int z; /* OK, z still has external linkage */
int z;        /* OK, z still has external linkage */
static int z; /* ERROR, z has external linkage */

int w;        /* w has external linkage */
extern int w; /* OK, w still has external linkage */
int w;        /* OK, w still has external linkage */
static int w; /* ERROR, w has external linkage */

In order to get sparse to issue the correct errors, a lot more
code would be needed. I'm sure I could add the necessary code
eventually, but it would be better for someone who understands
the code better than me to implement this...

I prefer to provide solutions, but in this case I will have to
make do with just reporting the issue :(

Random question: I noticed the preprocessor symbols were defined
frequently; every call to preprocess() in fact: is this intended?

HTH

ATB
Ramsay Jones

P.S. I've attached extern-test.c so you can try it out yourself.
I've tried it with 5 different compilers and the results are
very uneven! Older versions of gcc (circa 3.4) don't get it
completely correct, for example.
int f(void) { extern float g(int); return 0; }
int g(void) { return -1; } /* ERROR, conflicting type for g() */
int h(void) { extern double g(void); return 0; }  /* ditto */

int x;  /* the extern decl. below conflicts with this x, not param */
int k(int x)
{
	{ extern float x; x=1; }  /* ERROR, conflicting type for x */
	return x;  /* this is the parameter x */
}

static int y; /* y has internal linkage */
extern int y; /* OK, y still has internal linkage */
static int y; /* OK, y still has internal linkage */
int y;        /* ERROR, y has internal linkage */

extern int z; /* z has external linkage */
extern int z; /* OK, z still has external linkage */
int z;        /* OK, z still has external linkage */
static int z; /* ERROR, z has external linkage */

int w;        /* w has external linkage */
extern int w; /* OK, w still has external linkage */
int w;        /* OK, w still has external linkage */
static int w; /* ERROR, w has external linkage */
extern __inline__ int f(int);

extern __inline__ int
f(int x)
{
	return x;
}

Comments

Christopher Li July 6, 2009, 9:32 a.m. UTC | #1
On Sun, Jul 5, 2009 at 4:06 PM, Ramsay Jones<ramsay@ramsay1.demon.co.uk> wrote:
>
> pre-process.c:609 in function expand():
>        struct arg args[nargs];

That is a known limit of sparse. It does not handle dynamic sized array.
In kernel that is not a good thing because kernel stack is very limited.

> sparse:
>        cgcc -no-compile $(ALL_CFLAGS) $(SPARSE_FLAGS) $(SRC_C)

I think libgit2 should make sparse to compile the file one by one.
Do them all at once might overflow your memory.

>
> this results in the follwing warnings (on cygwin):
>
> .../byteorder.h:39:1: warning: multiple definitions for function '__ntohl'
> .../byteorder.h:39:1:  the previous one is here
> .../byteorder.h:56:1: warning: multiple definitions for function '__ntohs'
> .../byteorder.h:56:1:  the previous one is here

GNU C has special treatment for "extern inline". It does not generate
the stand alone copy of the function. Sparse does not handle that very
well yet. I think the easiest way to fix is just run those source file
in sparse one by one.


>
> The third hunk of the diff to parse.c actually triggers the problem,
> but is not the real cause of the regression.

I think that is the right thing to do. The problem is that sparse currently
does not have way to mark a function as inline only without the standalone
copy.


>
> I think the problem is mainly caused by bind_symbol() binding the
> global_scope to some symbols. Indeed, the global_scope/file_scope
> seems to be a bit confused and confusing. Note that the global_scope

Global scope is for store global visible symbols.
File scopes is use for file local symbols like static functions.

> is not changed at all once intialised. In particular, each translation
> unit keeps adding to the, one and only, global_scope; which is
> effectively the same as the builtin_scope!

What is wrong with only one global scope?

Chris

> Hmm, the following diff shows a quick fix:
>
> --->8---
> diff --git a/scope.c b/scope.c
> index 27e38bc..fb4c039 100644
> --- a/scope.c
> +++ b/scope.c
> @@ -49,6 +49,7 @@ void start_file_scope(void)
>        /* top-level stuff defaults to file scope, "extern" etc will choose global scope */
>        function_scope = scope;
>        block_scope = scope;
> +       start_scope(&global_scope);
>  }
>
>  void start_symbol_scope(void)
> @@ -87,6 +88,7 @@ static void end_scope(struct scope **s)
>  void end_file_scope(void)
>  {
>        end_scope(&file_scope);
> +       end_scope(&global_scope);
>  }
>
>  void new_file_scope(void)
> --->8---
>
> But this is not the correct fix. In fact it may have broken c2xml
> and ctags. I haven't checked, but look at the main() code in c2xml.c
> and ctags.c; again the semantics of global_scope vs. file_scope
> seems confused and confusing. At least to me. ;-)

As you said, that is not the right fix. Ctags needs to use global scope
to look for symbol has been define but does not used in the file.

Chris
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Christopher Li July 7, 2009, 6:27 a.m. UTC | #2
Can you try the following patch? I move the extern inline function
to file scope. Again, I still think it would be better let sparse
check one file at a time.

Chris
Ramsay Jones July 8, 2009, 7:32 p.m. UTC | #3
Christopher Li wrote:
> Can you try the following patch? I move the extern inline function
> to file scope. Again, I still think it would be better let sparse
> check one file at a time.
> 

This solves the problem on cygwin. I almost didn't test it on Linux,
since it looked like this should work just fine...

Unfortunately, the problem persists on Linux and I don't have time
tonight to try and debug it; I will look into it further, hopefully
tomorrow evening.

ATB,
Ramsay Jones

--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Christopher Li July 8, 2009, 8:15 p.m. UTC | #4
On Wed, Jul 8, 2009 at 12:32 PM, Ramsay Jones<ramsay@ramsay1.demon.co.uk> wrote:
> Unfortunately, the problem persists on Linux and I don't have time
> tonight to try and debug it; I will look into it further, hopefully
> tomorrow evening.

If you can give me a small test case on Linux that will be great.
I can pick it up from there.

Chris
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ramsay Jones July 10, 2009, 8:53 p.m. UTC | #5
Christopher Li wrote:
> On Wed, Jul 8, 2009 at 12:32 PM, Ramsay Jones<ramsay@ramsay1.demon.co.uk> wrote:
>> Unfortunately, the problem persists on Linux and I don't have time
>> tonight to try and debug it; I will look into it further, hopefully
>> tomorrow evening.
> 
> If you can give me a small test case on Linux that will be great.
> I can pick it up from there.
> 

[sorry for the late reply; something came up!]

I forgot to mention that, on Linux, enabling optimization is required to
trip this, since the extern inline function definitions are #ifdef'ed out
otherwise. That does not affect the following test case, however.

I've added to the previous test.c file (Note that the declaration of
g() does not include __inline__):

$ cat -n test.c
     1	
     2	extern __inline__ int f(int);
     3	
     4	extern __inline__ int
     5	f(int x)
     6	{
     7		return x;
     8	}
     9	
    10	
    11	extern int g(int);
    12	
    13	extern __inline__ int
    14	g(int x)
    15	{
    16		return x;
    17	}
    18	
$ ./sparse test.c
$ ./sparse test.c test.c
test.c:14:1: warning: multiple definitions for function 'g'
test.c:14:1:  the previous one is here
$ cp test.c test-again.c
$ ./sparse test.c test-again.c
test-again.c:14:1: warning: multiple definitions for function 'g'
test.c:14:1:  the previous one is here
$ 

Actually, the following may be closer to the situation on Linux:

$ cat -n test.c
     1	
     2	extern __inline__ int f(int);
     3	
     4	extern __inline__ int
     5	f(int x)
     6	{
     7		return x;
     8	}
     9	
    10	
    11	extern int g(int);
    12	
    13	#ifdef __OPTIMIZE__
    14	extern __inline__ int
    15	g(int x)
    16	{
    17		return x;
    18	}
    19	#endif
    20	
$ ./sparse test.c test.c
$ ./sparse -O2 test.c test.c
test.c:15:1: warning: multiple definitions for function 'g'
test.c:15:1:  the previous one is here


HTH

ATB
Ramsay Jones
extern __inline__ int f(int);

extern __inline__ int
f(int x)
{
	return x;
}


extern int g(int);

extern __inline__ int
g(int x)
{
	return x;
}
Christopher Li July 10, 2009, 11:27 p.m. UTC | #6
On Fri, Jul 10, 2009 at 1:53 PM, Ramsay Jones<ramsay@ramsay1.demon.co.uk> wrote:
>    11  extern int g(int);
>    12
>    13  extern __inline__ int
>    14  g(int x)
>    15  {
>    16          return x;
>    17  }
>    18

Err, that is ugly. I just find out what happen there.
The none inline version picks up the function definition
from the inline version because they are both "extern".
Sparse only has one global extern scope.

Later even the inline version get remove from the
filescope, the second file still conflict with the non-inline
version of the function body, which steal from the inline
version.

I add two lines to distinguish the "extern inline" vs "extern".
They are not the same_symbol any more. Because effectly
the "extern inline" is not visiable in the global "extern" scope.
Any one see problem using this approach?

I attach the patch follows. Can you give it a try?

Chris
Ramsay Jones July 13, 2009, 5:43 p.m. UTC | #7
Christopher Li wrote:
> I attach the patch follows. Can you give it a try?
> 

Yep, this works.
Tested on cygwin and Linux.

Thanks!

ATB,
Ramsay Jones

--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/scope.c b/scope.c
index 27e38bc..fb4c039 100644
--- a/scope.c
+++ b/scope.c
@@ -49,6 +49,7 @@  void start_file_scope(void)
 	/* top-level stuff defaults to file scope, "extern" etc will choose global scope */
 	function_scope = scope;
 	block_scope = scope;
+	start_scope(&global_scope);
 }
 
 void start_symbol_scope(void)
@@ -87,6 +88,7 @@  static void end_scope(struct scope **s)
 void end_file_scope(void)
 {
 	end_scope(&file_scope);
+	end_scope(&global_scope);
 }
 
 void new_file_scope(void)