[llvm-commits] Size of long double (X86_FP80Ty)

Duncan Sands baldrick at free.fr
Fri Sep 28 13:04:16 PDT 2007


Hi Dale, on my machine (x86-32, linux) LLVM considers long double to
have a size of 10 bytes and an alignment of 4 bytes.  GCC gives it a
size of 12 bytes and an alignment of 4 bytes (of course only the first
80 bits (10 bytes) are actually used - the rest is padding).

I can see several problems with a 10 byte size.

First, problems that aren't related to gcc.  In an array of long double,
the elements may not be aligned even if the array is aligned.  This is
because the usual invariant "the size is a multiple of the alignment" does
not hold for size 10, alignment 4.  I'm betting that the logic for working
out alignments of loads and stores in LLVM is not expecting this!  Since
this type only exists on x86, which is pretty lenient about unaligned
pointer access, I suppose this might not really matter.  Isn't unaligned
access slower than aligned access though?  Anyway, it seems to me much
better to avoid this can of worms and have the size be a multiple of the
alignment.

Second, problems related to gcc.  We convert long double to X86_FP80Ty
(which I've been referring to as long double).  The result is that the size
of the gcc type is not equal to the size of the LLVM type.  This causes all
kinds of problems with arrays and pointer arithmetic.   That's the reason
for the check that sizes are the same (which you turned off in this case,
tut tut!).  For example, suppose you declare an array of long doubles,
cast to an i8* and start moving around in it based on sizeof(long double).
Well you'll end up at the wrong place because sizeof is the gcc size (12)
but LLVM will have placed the elements 10 bytes apart!  This also has
knock-on effects, like LLVM arrays of long doubles having different
lengths to the corresponding gcc arrays etc.  If you think about it I
hope you will agree that it is important to convert gcc types to LLVM
types of the same size.

By the way, are there any standards that require size to be a multiple
of alignment?

The solution I would prefer is: make getTypeSize return 12 for long double,
have getTypeSizeInBits return 80 (I think this should be renamed to
getBitsUsedByType or something like that, see APInt comments below).  Make
it a requirement that getTypeSize always returns a multiple of the alignment.
Document that getTypeSize is the offset between successive elements in arrays
of this type, or between fields in a struct; and that loads and stores of this
type can write up to this many bytes.  Document that getBitsUsedByType returns
the minimum number of bits needed to hold all values of this type, and that
this can be less than 8*getTypeSize.  Correct all places that assume that
getBitsUsedByType is 8*getTypeSize (like the check that the gcc type has
the same size as the llvm type, which should be using 8*getTypeSize).

Note that this is exactly how it is already for APInt.  Consider a type
like i36.  For this, getTypeSize returns 8, i.e. 64 bits, while
getTypeSizeInBits returns 36.  Thus getTypeSize corresponds to gcc's
TYPE_SIZE while getTypeSizeInBits corresponds to TYPE_PRECISION.  This
seems like a good model to me.

That said, other solutions are possible.  For example, we could ignore
the unaligned array element problem, and alter gcc so that TYPE_SIZE
for long double becomes 80 bits.  I have no idea what kind of problems
this might cause, if any.

Or we could convert long double to a struct { X86_FP80Ty, i16 }, and
fix up all the floating point stuff in llvm-convert.  Chris mentioned
to me on IRC that this is a pessimisation in general, and would rather
have X86_FP80Ty be used for scalar operations (so you can continue to
use registers) but have { X86_FP80Ty, i16 } be used for loads and stores
to memory.  He also noted that this kind of thing would be also helpful
for Darwin's funky 32 bit boolean type (which would be more optimally
manipulated as in i1 in scalar expressions, but needs to be stored as
32 bits).  Thus he suggested associating two LLVM types with each primitive
gcc type: a type used for lvalues (i.e. memory access) and a type used for
scalar operations (LLVM register operations).  The lvalue type would always
have the same size as the gcc type, while the scalar type could differ in size.

What do you think?

Best wishes,

Duncan.



More information about the llvm-commits mailing list