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.

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:

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:

(*) 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:

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:

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.

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:

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)?

None: Ieee128PowerPC (last edited 2015-10-13 18:45:18 by MichaelMeissner)