IEEE 128-bit floating point support for PowerPC
Over 2014 and 2015, I (Michael Meissner) have been busy adding IEEE 754R 128-bit floating point support to the PowerPC GCC compiler. This page is an attempt to document the current progess and raise issues with other groups such as the glibc developers and gdb developers.
Section Links
1.0 Background
1.1 PowerPC History
The PowerPC GCC compiler for Linux, AIX, and Darwin do not use the IEEE 754R 128-bit floating point type for the long double data type. Instead the PowerPC compiler uses a software type known as IBM extended double type. The IBM extended double type is a pair of double values that give the user more bits of precision, but no additional range for the mantissa. All of the support for IBM extended double is done via software emulation (there are deprecated instructions to load and store a pair of floating point values, but current hardware no longer supports these instructions).
1.2 Intel/AMD History
The x86 Intel/AMD hardware originally only supported floating point via a co-processor (80387) that supported an 8 deep LIFO stack of 80 bit floating values. Operations included:
- Loading single, double, and extended precision values and pushing them onto the stack;
- Doing an operation that popped off the top one or two values off of the stack, and then pushed the result back on to the stack;
- Popping off the top value of the stack and storing it as a single, double, or extended precision value.
The long double type is the 80 bit extended precision type used by the floating point stack.
The SSE instructions were added to do single and double precision operations on floating point registers. The floating point stack is now only used for long double support.
A few years ago, the x86 GCC compiler developers added two new types: __float80 and __float128.
The __float80 type corresponds to the original long double type. The suffix w or W is used to indicate that the constant should be converted to the 80-bit format.
The __float128 type corresponds to the IEEE 754R 128-bit floating point type. The suffix q or Q is used to indicate that the constant should be converted to the 128-bit format.
The options -mlong-double-80 and -mlong-double-128 were added to control the default of long double.
1.3 Sizes
If you compare IBM extended double to IEEE 754R 128-bit floating point you get:
Type
Size in bytes
Exponent
Mantissa
Rough minimum
Rough maximum
Decimal digits
IBM extended double
16 bytes
11 bits
106 bits (53 + 53)
1.0E−323L
1.0E+308L
30-34
IEEE 754R 128-bit
16 bytes
15 bits
113 bits
1.0E-4932Q
1.0E+4932Q
33-36
Intel 80-bit long double
12/16 bytes (*)
15 bits
63 bits + leading 1 bit
1.0E-4932W
1.0E+4932W
19-21
(*) The x86 compiler uses a size of 12 bytes in 32-bit mode, and 16 bytes in 64-bit mode. The instructions used only use 80-bits of the data.
1.4 Original PowerPC GCC long double support
Internally within the GCC compiler, the TFmode type is used to represent the default 128-bit floating point type.
On older Linux systems, the default for long double was to use the double type.
In the past, the default for Linux and AIX systems was changed, and the new default for long double is the IBM extended double type. There are switches (-mlong-double-64 and -mlong-double-128) to control the default for long double.
On the RTEMS system embedded system, the default for long double is double. If you use the -mlong-double-128 option, the default for long double is IEEE 754R 128-bit floating point. It is believed that this is the only PowerPC variant that is supported by GCC that used IEEE 754R. It is not clear if any RTEMS user has ever used the IEEE 754R 128-bit support.
Internally within GCC, the TFmode type is used to represent the default 128-bit floating point support. This means there are a parallel set of TFmode operations for doing IBM extended double and for doing IEEE 754R operations.
2.0 Current GCC work
This section describes the current GCC work to bring people up to speed. The __float128 support is only available on systems supporting the Vector/Scalar (VSX) instruction set that was introduced with the Power7 server architecture.
2.1 Options and defaults
The option -mfloat128 was added to enable IEEE 754R support It is currently not default, but when the final patches are committed, it will be default if the VSX instruction set is enabled.
The compiler already supports two undocumented options (-mabi=ieeelongdouble and -mabi=ibmlongdouble) to control whether long double has the IBM extended double type or uses the IEEE 754R 128-bit format.
2.2 Types
If -mfloat128 is enabled, two new types are added:
Keyword
Type
Explanation
__float128
TFmode
IEEE 754R 128-bit floating point. Like the x86, the suffix q or Q denotes __float128 constants
__ibm128
IFmode
IBM extended double. At present, no suffix is provided for explicit __ibm128 constants
The long double type at present retains its current default, and the TFmode type internally switches between the two types.
Internally within the compiler there are 3 types:
Type
Explanation
IFmode
Always IBM extended double
KFmode
Always IEEE 754R 128-bit floating point
TFmode
Matches the default for long double
If -mabi=ieeelongdouble, TFmode is IEEE 754R 128-bit floating point, and the L/l suffixes produces IEEE 754R constants
If -mabi=ibmlongdouble, TFmode is IBM extended double, and the L/l suffixes produces IBM extended double constants
If the -mfloat128 option is in effect, IEEE 128-bit floating point values are passed and returned as vector types (i.e. the value is passed by value in Altivec registers v2 through v11 (VSX registers vs34 through vs43) and the result is returned in Altivec register v2 (VSX register vs34).
Under RTEMS, IEEE 754R 128-bit floating point is stored on the stack and a pointer to the 16-byte area is passed by reference. Returns expect a pointer to a 16-byte area in which the result is stored.
2.3 Emulation functions
In the GCC naming scheme, the emulation functions typically have a two letter combination to represent the mode. The traditional name for 16-bit double is TFmode, and the functions typically have a tf in the name. For example, __addtf3 would be a function that adds 2 16-bit floating point scalar values.
When the original long double changes were made, the emulation functions were changed to either _gcc_q<x> or _q_<x> depending on whether long double was IBM extended double or IEEE 754R 128-bit. However, not all names were changed. This means we cannot use the default names with tf for the emulation functions for the new __float128 data type. I elected to switch all of the __float128 emulation names to use kf instead of tf. The exception is the conversions between __float128 and __ibm128, where the __ibm128 part uses the tf name.
In order to simplify processing, I modified the compiler use a single compare function for __float128. The result of this compare function are values 0..15 that mirror the bits in the PowerPC condition code registers. There are two comparison functions, __cmpukf2 is intended to mirror the fcmpu instruction in doing an unordered comparison, while __cmpokf2 is intended to mirror the fcmpo instruciton in doing an ordered comparison.
Function
Bits
PowerPC long double names
PowerPC __float128 names
PowerPC RTEMs names
x86 names
convert float to __float128
both
inline code
__extendsfkf2
_q_stoq
__extendsftf2
convert double to __float128
both
inline code
__extenddfkf2
_q_dtoq
__extenddftf2
convert __float128 to float
both
inline code
__trunckfsf2
_q_qtos
__trunctfsf2
convert __float128 to double
both
inline code
__trunckfdf2
_q_qtod
__trunctfdf2
convert __float80 to __float128
both
---
---
---
__extendxftf2
convert __float128 to __float80
both
---
---
---
__trunctfxf2
convert __ibm128 to __float128
both
---
__extendkftf2
---
---
convert __float128 to __ibm128
both
---
__trunctfkf2
---
---
convert int to __float128
both
inline code
__floatsikf
_q_itoq
__floatsitf
convert long long to __float128
both
__floatditf
__floatdikf
__floatditf
__floatditf
convert long to __float128
64-bit
__floatditf
__floatdikf
---
__floatditf
convert __int128 to __float128
64-bit
__floattitf
__floattikf
---
__floattitf
convert unsigned int to __float128
both
inline code
plus __gcc_qadd__floatunsikf
_q_utoq
__floatunsitf
convert unsigned long long to __float128
both
__floatunditf
__floatundikf
__floatunditf
__floatunditf
convert unsigned long to __float128
64-bit
__floatunditf
__floatundikf
---
__floatunditf
convert unsigned __int128 to __float128
64-bit
__floatuntitf
__floatuntikf
---
__floatuntitf
convert _Decimal32 to __float128
both
__dpd_extendsdtf
__dpd_extendsdkf
---
__bid_extendsdtf
convert _Decimal64 to __float128
both
__dpd_extendddtf
__dpd_extendddkf
---
__bid_extendddtf
convert _Decimal128 to __float128
both
__dpd_trunctdtf
__dpd_trunctdkf
---
__bid_trunctdtf
convert __float128 to _Decimal32
both
__dpd_trunctfsd
__dpd_trunckfsd
---
__bid_trunctfsd
convert __float128 to _Decimal64
both
__dpd_trunctfdd
__dpd_trunckfdd
---
__bid_trunctfdd
convert __float128 to _Decimal128
both
__dpd_extendtftd
__dpd_extendkftd
---
__bid_extendtftd
add __float128
both
__gcc_qadd
__addkf3
_q_add
__addtf3
subtract __float128
both
__gcc_qsub
__subkf3
_q_sub
__subtf3
multiply __float128
both
__gcc_qmul
__mulkf3
_q_mul
__multf3
divide __float128
both
__gcc_qdiv
__divkf3
_q_div
__divtf3
negate __float128
both
inline code
inline code
inline code
inline code
absolute value __float128
both
inline code
inline code
inline code
inline code
__float128 comparison
both
---
__cmpukf2, __cmpokf2
---
---
__float128 equal
both
inline code
_q_feq
__eqtf2
__float128 not equal
both
inline code
_q_fne
__netf2
__float128 less then
both
inline code
_q_flt
__lttf2
__float128 less then or equal
both
inline code
_q_fle
__letf2
__float128 greater then
both
inline code
_q_fgt
__gttf2
__float128 greater then or equal
both
inline code
_q_fge
__getf2
__float128 unordered
both
inline code
__unordtf2
__unordtf2
3.0 Emulation changes in libgcc and glibc
In general, the current emulation functions that are located in the glibc project and copied to the libgcc project work well to support IEEE 754R 128-bit floating point on PowerPC.
A few changes need to be done in order to support the current gcc work, to change the names of the functions and the TFmode type and to insure that the functions are compiled with VSX instruction support.
There are 4 alternative approaches that can be done to provide these changes. From a development point of view, the 3 approaches are fairly similar in terms of work. It depends on which is more acceptable to the glibc community.
In addition to the naming changes, there are 4 functions (__extendkftf2, __trunctfkf2, __cmpukf2, and __cmpokf2) that will need to be provided.
The __extendfktf2 function converts from IBM extended double to IEEE 754R 128-bit binary floating point.
The __trunctfkf2 converts from IEEE 754R 128-bit binary floating point to IBM extended double.
The __cmpukf2 function compares two IEEE 754R 128-bit binary floating point values, and returns a value of 0..15 that mirrors what the fcmpu instruction returns for an unordered comparison.
The __cmpuof2 function compares two IEEE 754R 128-bit binary floating point values, and returns a value of 0..15 that mirrors what the fcmpo instruction returns for an ordered comparison.
3.1 Clone the functions
Right now, the libgcc/soft-fp directory contains 27 functions to implement the various TFmode emulation functions. These functions are very simple, and most of the work is done in the macros defined in the various header files like quad.h.
KFtype
__addkf3 (KFtype a, KFtype b)
{
FP_DECL_EX;
FP_DECL_Q (A);
FP_DECL_Q (B);
FP_DECL_Q (R);
TFtype r;
FP_INIT_ROUNDMODE;
FP_UNPACK_SEMIRAW_Q (A, a);
FP_UNPACK_SEMIRAW_Q (B, b);
FP_ADD_Q (R, A, B);
FP_PACK_SEMIRAW_Q (r, R);
FP_HANDLE_EXCEPTIONS;
return r;
}One method to provide the __float128 emulation functions on PowerPC is just to clone these functions and change the name from __addtf3 to __addkf3. The problem with doing this is having the extra files, and having to modify these files as well as the TFmode functions if the underlying support macros in the header files changed.
3.2 Include the files with #define substitution
Instead of cloning the functions, we can add the various KFmode functions to include the original TFmode functions after doing #define's to change the names. For example:
/* PowerPC __float128 add support (... standard copyright assignment). */
#ifdef _ARCH_PPC
/* On PowerPC, insure that the KF functions are compiled with the -mfloat128 support. */
#pragma GCC target ("vsx,float128")
#endif /* _ARCH_PPC */
/* Use __attribute__((mode(KF))) for TFmode. */
#define TF KF
/* Change the name. */
#define __addtf3 __addkf3
#include <addtf3.c>
3.3 Create the *KF* files in the Makefile via sed
We don't have to actually have addkf3.c and friends checked into the tree. We can just create these files in the Makefile, and use the sed command to change all of the names. This is my current approach.
3.4 Add #defines to current TF functions or to sfp-machine.h
Rather than having separate files, we could just embed the appropriate #define options in the current TF files.
If there is some common #define that can be inspected, we could bury all of the magic into the sfp-machine.h file for PowerPC. If this approach is used, there would be no changes needed to the common files. It would make debugging the PowerPC files trickier because of the defines.
4.0 Front end changes
There are at least 2 potential changes that are needed for the front ends.
4.1 Name mangling
One of the decisions that need to be made is how __float128 is mangled for C++ support. Similarly if the -mabi=ieeelongdouble option is used, how does this affect name mangling.
The standard for mangling uses "e" for the standard long double type. The PowerPC backend uses "g" when long double is the IBM extended double type.
At the moment, I use "U10__float128" for __float128 and if -mabi=ieeelongdouble is used, I also use it for long double. I could use "e" instead, but I wasn't sure if it would confuse things with the original IEEE 754R support used in the RTEMS PowerPC port. Other PowerPC compilers (the IBM XL compiler and LLVM) also would like to use a standard approach to name mangling if/when they add IEEE 754R support.
4.2 Other languages than C/C++
The C and C++ languages support the __float128 keyword that is enabled with -mfloat128 support. I don't know if it is desirable to add __float128 support to the other languages, such as Fortran or Ada.
5.0 Glibc changes outside of emulation
There are various things that needs to be done in glibc beyond the simple emulation functions.
5.1 Float128 math functions
Ideally glibc should export __float128 versions of the math library. We need a naming scheme for these functions. The current library uses a suffix of "f" for functions that take a float argument instead of double, and "l" for functions that take a long double argument. Do we want to define a new suffix, such as "float128" or "f128" for these math functions.
5.2 Glibc internal use of long double
Right now, glibc uses long double in some of the double math functions to provide more accuracy. Should glibc transition to explicitly use __float128 for these types, or possibly be compiled with -mabi=ieeelongdouble?
6.0 Gdb and debugging issues
One issue that has come up is that for standard types, the Dwarf debugging format just passes a size. Unfortunately, the PowerPC has two floating point scalar types that are both 16 bytes, and there is no way to know if the type was used the IBM extended double representation or the IEEE 754R representation.
7.0 Transition to IEEE 128-bit floating point as the default for long double
In the GCC 6.x time frame, it is expected that default for long double will remain IBM extended double.
In the GCC 7.x time frame, I would like to explore changing the default on at least some of the PowerPC platforms to be IEEE 754R 128-bit floating point. I believe that the following defaults should be used:
Platform
Default for long double
PowerPC 64-bit Linux big endian server
Remain with IBM extended double
PowerPC 32-bit Linux big endian server
Remain with IBM extended double
PowerPC 32-bit embedded
Whatever the default for the platform is should remain
PowerPC 64-bit Linux little endian server
It may be desirable to change the default to IEEE 754R 128-bit floating point
AIX
Remain with IBM extended double
Darwin
Remain with IBM extended double
However, in order to do the transition to IEEE 754R 128-bit floating point, the distribution must would need to fully support __float128. This presumably means, the transition would only occur when the distribution fully supports __float128 in both the compiler and library.
The Advance Toolchain from IBM, may be a way for users who desire long double to be IEEE 754R 128-bit floating point by default, rather than waiting for the next major release of their distribution.
7.1 Configuration switches
There should be a configuration switch that changes the default for what long double is. This switch should in fact be implemented in the GCC 6.x time frame, but the default in GCC 6.x would remain IBM extended double
7.2 Preventing accidental linking IBM extended double with IEEE 754R
It would be nice if there was someway to flag C code that was compiled that uses the long double type with -mabi=ieeelongdouble from being linked with code that was compiled with -mabi=ibmlongdouble. Code that uses __float128 explicitly and does not use long double should be allowed to be linked. We will also need some way for the same library to be used with either default. I am not familiar with the mechanisms used in glibc for such versioned symbols.
This is mostly for C, since C++ name mangling should give a link error if the wrong default was used.
7.3 Printf and scanf
The printf and scanf family of functions define format modifiers for long double. If the long double type transitions to the IEEE 754R 128-bit floating point format, there should be some way to inform the library of the long double representation.
In addition to the default for long double, it may be desirable to provide a format modifier to print and read in explicit __float128 types.
7.4 Math.h
Currently, the include file math.h auto-magically switches between math functions when the default for long double is double. Once glibc has added support for __float128 versions of the math library, the include math.h should switch to use the IEEE 754R version of the functions.
One potential problem is if the user explicitly calls sinl without using the math.h include file, they might not get the appropriate function.
When the transition is made, do we want to rename the existing functions, or will be stuck forever using sinfloat128 instead of sinl.
It may make sense to have explicit __ibm128 versions of the math functions, and have math.h rename the 'l' functions to those names.
8.0 Other Issues
This section is a grab bag of other issues.
8.1 Fused multiply-add
Should we provide fused multiply-add functions in the soft-fp functions, as well as the library?
8.2 Backport changes to older branches
Is it desirable to backport the __float128 changes to the older branches (GCC 4.9, GCC 5.x)?