This is the mail archive of the
gcc-bugs@gcc.gnu.org
mailing list for the GCC project.
Pointer type problem (plain C on i386)
- To: gcc-bugs at gcc dot gnu dot org
- Subject: Pointer type problem (plain C on i386)
- From: Balazs Takacs <s8513tak at hszk dot bme dot hu>
- Date: Mon, 19 Feb 2001 14:46:47 +0100 (MET)
SYNOPSIS: GCC 2.95 (and later) handles pointer operations incorrectly
SEVERITY: serious
PRIORITY: medium
CATEGORY: c / target (?)
CLASS: wrong-code
RELEASE: gcc version 2.95.2 19991024 (or higher)
Dear developers,
First of all, I'd like to apologize for the overly verbose nature of this
bug report. Probably it will take longer to read this file than actually
correct the problem, but since this bug produces no blatant evidence, the
situation requires closer explanation.
Warning -- this is definitely not a false alarm, this bug does exist even
in the current GCC snapshot used at http://www.codesourcery.com/. Please
do not ignore this message simply because of its size.
(1) A brief introduction
Maybe it would have been wise to post a question to the appropriate forum
asking whether the following C expressions are equivalent on an i386:
> #define DIO_HIWORD(x) ((unsigned short *) &(x))[1]
> #define DIO_HIWORD(x) (*(((unsigned short *) &(x)) + 1))
> #define DIO_HIWORD(x) (*((unsigned short *) (((void *) &(x)) + 2)))
To the best of my knowledge, these *are* equivalent; they're intended for
accessing the higher short (16-bit) word of a long (32-bit) object. The
first and the second expressions differ merely in the use of a "syntactic
sugar", while the third one contains a redundant typecast.
I noticed, however, that under certain circumstances, gcc 2.95.2 compiles
the latter two macros into different assembly code -- one of which is, in
my view, incorrect. In addition, the current snapshot of GCC appears to
compile all three versions incorrectly.
I've created three different C files using the three versions of my above
mentioned DIO_HIWORD() macro -- they're called best.c, good.c and runs.c,
respectively. The names suggest that while the former ones are easier to
understand, only the third version appears to run correctly. I've tested
several versions of GCC; each of them compiled best.c and good.c into the
same (broken or working) assembly code -- so we can eliminate best.c from
the further discussion.
Despite the warning in http://gcc.gnu.org/bugs.html, I decided to attach
multiple assembly files, as an archive. Sorry for this counter-effective
method, but that's the most obvious way for pointing out the symptom that
I consider a compiler bug. The absolute minimum source file required for
reproducing the bug is `good.i', which is attached separately as well.
(2) Description of the problem
The bug seems to appear only if the macros above are used in conjunction
with volatile assembly code (outw). Consider the following program:
> long array[4] = { 0, 1, 2, 3 };
>
> extern inline void outw(unsigned short value, unsigned short port)
> {
> __asm__ __volatile__ ("outw %w0,%w1"
> : : "a" (value), "Nd" (port));
> }
>
> int main(void)
> {
> long data, *buf = array;
> unsigned int i;
> int iobase = 0x0308;
>
> for(i = 0; i < 4; i++) {
> data = *buf++;
> outw(DIO_LOWORD(data), iobase);
> outw(DIO_HIWORD(data), iobase);
> }
> return 0;
> }
This routine is supposed to send the contents of an array to an I/O port,
each long as two short words. Depending on which of the macros above is
used, the resulting executable will sometimes refuse to outw() the higher
short words correctly.
Let us compare the assembly outputs produced by gcc 2.95.2 (see (3) below
for a detailed description of the test setup):
> .file "good.c" .file "runs.c"
[...]
> main: main:
> subl $16,%esp subl $16,%esp
> pushl %edi pushl %edi
> pushl %esi pushl %esi
> pushl %ebx pushl %ebx
> movl $array,%ebx movl $array,%ebx
> xorl %edi,%edi xorl %edi,%edi
> movl $776,%ecx movl $776,%ecx
> movzwl 14(%esp),%esi leal 12(%esp),%esi (#1)
> .p2align 4,,7 .p2align 4,,7
> .L7: .L7:
> movl (%ebx),%eax movl (%ebx),%eax
> movl %eax,12(%esp) movl %eax,12(%esp)
> addl $4,%ebx addl $4,%ebx
> movl 12(%esp),%eax movl 12(%esp),%eax
> movl %ecx,%edx movl %ecx,%edx
> #APP #APP
> outw %ax,%dx outw %ax,%dx
> #NO_APP #NO_APP
> movl %esi,%eax movzwl 2(%esi),%eax (#2)
> #APP #APP
> outw %ax,%dx outw %ax,%dx
> #NO_APP #NO_APP
> incl %edi incl %edi
> cmpl $3,%edi cmpl $3,%edi
> jbe .L7 jbe .L7
> xorl %eax,%eax xorl %eax,%eax
[...]
The for() loop begins obviously at the L7 label. Both versions fetch the
next array element from (%ebx) and store it in %eax and the stack. Then
%edx gets loaded with the port number and the lower short word is sent to
the port. However, only the runs.s version will fetch the high word from
the freshly updated stack variable (addressed by %esi). The good.s code
uses a constant value of %esi instead: the contents of the stack variable
before executing the loop! Of course, placing the movzwl instruction at
(#1) in front of (#2) would correct the problem, but it isn't happening.
GCC doesn't seem to notice that storing %eax will alter four bytes in the
stack memory -- or that the ominous macro expression will reference this
four-byte area. Therefore it moves the fetch outside the loop, read the
higher data word beforehand and use it during the complete loop. This is
what I consider incorrect compiler behavior.
(3) Detailed test setup
In order to make sure that the problem isn't related with one particular
installation of GCC, I've tested two separate gcc 2.95.2 systems -- both
yielding the same results as shown above.
---->8----
System type: Debian GNU/Linux 2.2r0 (potato) for i386 (Intel i486DX/33)
GCC installation: from the standard binary package, no modifications
GCC configuration/build time options: unknown, see above (sorry)
GCC version number: gcc 2.95.2 20000220 (package: gcc_2.95.2-13.deb)
> $ gcc -v
> Reading specs from /usr/lib/gcc-lib/i386-linux/2.95.2/specs
> gcc version 2.95.2 20000220 (Debian GNU/Linux)
The complete command line used for compiling the programs:
> $ gcc -S -Wall -O2 -fomit-frame-pointer -fno-strength-reduce best.c
> $ gcc -S -Wall -O2 -fomit-frame-pointer -fno-strength-reduce good.c
> $ gcc -S -Wall -O2 -fomit-frame-pointer -fno-strength-reduce runs.c
Compiler messages: no warnings, no errors
---->8----
System type: FreeBSD 4.2-RELEASE for i386 (Intel iPentium/133)
GCC installation: from the standard binary package, no modifications
GCC configuration/build time options: unknown, see above (sorry)
GCC version number: gcc 2.95.2 19991024 (package: gcc-2.95.2.tgz)
> $ gcc -v
> Using builtin specs.
> gcc version 2.95.2 19991024 (release)
The complete command line used for compiling the programs:
> $ gcc -S -Wall -O2 -fomit-frame-pointer -fno-strength-reduce best.c
> $ gcc -S -Wall -O2 -fomit-frame-pointer -fno-strength-reduce good.c
> $ gcc -S -Wall -O2 -fomit-frame-pointer -fno-strength-reduce runs.c
Compiler messages: no warnings, no errors
---->8----
In addition to the above mentioned systems, I've compiled the test files
under the same conditions on a gcc 2.7.2.3 (Debian 2.1) and an egcs 1.1.2
(NetBSD 1.5) system. Both compiler versions produced identical and fully
functional executables from each of the C sources. I've also tested the
current GCC snapshot using CodeSourcery's online compiler:
> .ident "GCC: (GNU) 3.0 20010218 (prerelease)"
The resulting assembly sources for good.c and runs.c did match this time;
however, they *both* were wrong in the sense of failing to read the high
short word correctly!
You can find the appropriate C and assembly files in the attached source
archive. Preprocessed files are included, too; however, none of my tests
could discover any difference between the ways the preprocessors operate.
It seems pretty clear to me that the problem is within cc1 itself.
Best regards,
Balazs Takacs <s8513tak@hszk.bme.hu>
fifth-year student of information technology
Budapest University of Technology and Economics
# 1 "best.c"
long array[4] = { 0, 1, 2, 3 };
extern inline void outw(unsigned short value, unsigned short port)
{
__asm__ __volatile__ ("outw %w0,%w1" : : "a" (value), "Nd" (port));
}
int main(void)
{
long data, *buf = array;
unsigned int i;
int iobase = 0x0308;
for(i = 0; i < 4; i++) {
data = *buf++;
outw(((unsigned short *) &( data ))[0] , iobase);
outw(((unsigned short *) &( data ))[1] , iobase);
}
return 0;
}
# 1 "good.c"
long array[4] = { 0, 1, 2, 3 };
extern inline void outw(unsigned short value, unsigned short port)
{
__asm__ __volatile__ ("outw %w0,%w1" : : "a" (value), "Nd" (port));
}
int main(void)
{
long data, *buf = array;
unsigned int i;
int iobase = 0x0308;
for(i = 0; i < 4; i++) {
data = *buf++;
outw((*(((unsigned short *) &( data )) + 0)) , iobase);
outw((*(((unsigned short *) &( data )) + 1)) , iobase);
}
return 0;
}
# 1 "runs.c"
long array[4] = { 0, 1, 2, 3 };
extern inline void outw(unsigned short value, unsigned short port)
{
__asm__ __volatile__ ("outw %w0,%w1" : : "a" (value), "Nd" (port));
}
int main(void)
{
long data, *buf = array;
unsigned int i;
int iobase = 0x0308;
for(i = 0; i < 4; i++) {
data = *buf++;
outw((*((unsigned short *) (((void *) &( data )) + 0))) , iobase);
outw((*((unsigned short *) (((void *) &( data )) + 2))) , iobase);
}
return 0;
}
source.tar.gz