[cfe-users] Union aliasing issue in clang 3.3 on FreeBSD

Sami Vaarala sami.vaarala at gmail.com
Tue Dec 10 05:10:22 PST 2013


Hi,

I encountered a union aliasing issue on FreeBSD clang 3.3, see code
below.  In short, assigning a union value to another when the union
contains a double and an array of 8 bytes causes the value to copy
incorrectly.  This is dependent on compiler options, the behavior
doesn't happen without -m32 or when using -O0.  It also doesn't happen
with an older clang.

Is this a clang issue, or is my code making invalid assumptions about
unions and casting?  Based on a quick look at the generated asm code,
clang generates a floating point load/store to copy the value - could
this be the root cause?  The code looks something like:

 8048653:       dd 44 24 20             fldl   0x20(%esp)
 8048657:       dd 5c 24 10             fstpl  0x10(%esp)
 804865b:       8d 44 24 10             lea    0x10(%esp),%eax
 804865f:       89 04 24                mov    %eax,(%esp)
 8048662:       e8 61 ff ff ff          call   80485c8 <dump>

Thanks for any help :-)  I'm pasting the code below as I'm not sure
whether or not attachments are supported.

======== clang_aliasing.c
/*
 *  Problem with clang 3.3 on FreeBSD:
 *
 *    $ clang -v
 *    FreeBSD clang version 3.3 (tags/RELEASE_33/final 183502) 20130610
 *    Target: x86_64-unknown-freebsd10.0
 *    Thread model: posix
 *
 *  The problem manifests itself as follows (x86_64):
 *
 *    $ clang -Os -m32 -std=c99 -fstrict-aliasing -fomit-frame-pointer
clang_aliasing.c
 *    $ ./a.out
 *    11 22 33 44 00 00 f1 ff
 *    11 22 33 44 00 00 f9 ff   <==
 *    11 22 33 44 00 00 f9 ff   <==
 *    11 22 33 44 00 00 f1 ff
 *
 *  The value is corrupted even in the base care where 'a' is initialized
 *  as bytes and then copied with a structural assignment; the double part
 *  of the union is not accessed at all.  The corruption is always that the
 *  6th byte gets OR'd with 0x08.  This bit is the highest bit of the IEEE
 *  double mantissa, so this is probably floating point related.  The code
 *  generated uses a floating point load/store to copy the value except in
 *  the cases where the copy works.
 */

#include <stdio.h>
#include <stdint.h>

typedef union { double d; uint8_t c[8]; } my_union;

void dump(my_union *u) {
        int i;
        for (i = 0; i < 8; i++) { printf("%02x ", (int) u->c[i]); }
        printf("\n");
}

#define  COPY_FAILS(s,d) { int _i; for(_i = 0; _i < 8; _i++) { \
                ((uint8_t *) (d))[_i] = ((uint8_t *) (s))[_i]; \
        } }

#define  COPY_WORKS(s,d) { volatile int _i; for(_i = 0; _i < 8; _i++) { \
                ((uint8_t *) (d))[_i] = ((uint8_t *) (s))[_i]; \
        } }

int main(int argc, char *argv[]) {
        my_union a, b, c, d;

        /* Initial value, the contents of bytes 6 and 7 seem to matter */
        a.c[0] = 0x11; a.c[1] = 0x22; a.c[2] = 0x33; a.c[3] = 0x44;
        a.c[4] = 0x00; a.c[5] = 0x00; a.c[6] = 0xf1; a.c[7] = 0xff;
        dump(&a);

        /* Copy with assignment (does not work) */
        b = a; dump(&b);

        /* Copy with a byte-by-byte macro (does not work) */
        COPY_FAILS(&a, &c); dump(&c);

        /* Copy with a byte-by-byte macro (volatile loop variable - works) */
        COPY_WORKS(&a, &d); dump(&d);

        return 0;
}
========

-Sami



More information about the cfe-users mailing list