RFC: Decimal floating-point support (ISO/IEC TS 18661-2 and C23)

I’ve been tasked with implementing decimal floating-point (DFP) support for C (_Decimal32 et al) in Clang as specified by ISO/IEC TS 18661-2:2015 and as adopted for C23 as a conditionally supported feature (__STDC_IEC_60559_DFP__). Support for C++ (std::decimal32 et al) as specified by ISO/IEC TR 24733:2011 is a secondary goal. This post seeks comments on the initial design approach described below.

Please note that I am not an expert in decimal floating-point, so there is a good chance that I’ve made some incorrect assumptions and/or am uninformed about environments, challenges, and design options that an expert would be expected to know. Pointers to educational resources would be helpful and appreciated.

This RFC is intended to introduce considerations and establish high-level frontend direction. Specification, backend, and standard library implementation concerns are largely left for future RFCs or code reviews.

Introduction:

Some architectures (e.g., Power, z/Architecture) have hardware support for DFP, but other common architectures do not. A portable DFP implementation requires a software based implementation in addition to any hardware specific support. Hardware support may provide a subset of the operations required by the linked specifications, so a run-time support library may be a necessity regardless of target architecture.

The relevant standard for C support is ISO/IEC TS 18661-2:2015. This and related standards were recently added to C23. The added features include the following new decimal floating types and corresponding literals with the noted suffixes. The type names are keywords and type specifiers. These are arithmetic types (they are not storage types).

  • _Decimal32; literal suffix: df (e.g., 1.0df, 2.0DF).
  • _Decimal64; literal suffix: dd (e.g., 1.0dd, 2.0DD).
  • _Decimal128; literal suffix: dl (e.g., 1.0dl, 2.0DL).

These new types are accompanied by _Decimal32_t and _Decimal64_t typedefs conditionally declared in math.h with target types dependent on the DEC_EVAL_METHOD macro. A number of new macros are also specified, some of which depend on target capabilities (for example, FP_FAST_FMAD32 is specified to be defined if the _Decimal32 fma() function “executes about as fast, or faster than, a multiply and an add of double operands”).

Formatted input/output functions (printf() and friends) will gain new %H, %D, and %DD length modifiers. These will require updates to -Wformat-invalid-specifier diagnostics.

Compilers known to implement the C DFP core language support include gcc, Intel’s classic icc compiler, and IBM’s xlC (via the -qdfp option).

Libraries known to implement the C DFP library support include libdfp, the libdecimal library included with Intel’s classic icc compiler, and the libc standard library included with IBM’s AIX OS.

The relevant standard for C++ DFP support is ISO/IEC TR 24733:2011 (I believe N2489 to be the latest draft). Unlike C, the DFP types are specified as library types rather than builtin types and there is no support for literals nor user-defined literals (UDLs). A proposal (N3871) to merge ISO/IEC TR 24733:2011 into the C++ working paper with support for C++11 UDLs (with the same lowercase and uppercase spellings of the C literal suffixes; no mixed case) was submitted to WG21 in 2014 but has not yet been adopted.

  • class std::decimal::decimal32; no UDL.
  • class std::decimal::decimal64; no UDL.
  • class std::decimal::decimal128; no UDL.

Libraries known to implement the C++ DFP support include libstdc++, IBM’s decNumber++, and Bloomberg’s bdldfp (implemented in a non-std namespace).

In libstdc++, these classes are implemented as light wrappers around builtin types spelled as follows. Operations on the builtin types are implemented via intrinsic functions. These are arithmetic types and they participate in type deduction and overloading (they are not storage types).

  • float __attribute__((mode(SD))) # _Decimal32.
  • float __attribute__((mode(DD))) # _Decimal64.
  • float __attribute__((mode(TD))) # _Decimal128.

For compatibility with gcc and libstdc++, it will be necessary for Clang to support these attributes.

The Intel classic icc compiler uses a different syntax for these builtin types in C++, but compatibility with icc is not a goal at this time.

DFP operations may be performed in constant expressions, so it is necessary to be able to perform such operations at translation-time (and constant folding support would be desirable anyway). This will require a host and target independent capability to manipulate DFP values.

The referenced standards do not require a particular value representation for DFP types. There appear to be two encodings currently in use; binary integer decimal (BID) and densely packed decimal (DPD).

Run-time support libraries:

I have identified two DFP libraries that are generally in use for C and C++ today, both of which have been forked and are distributed from multiple sources. These don’t have particularly strong names, so I’ve categorized them below by their original contributors.

  1. Intel
    Provides support for BID encoded DFP values and functions to convert to/from DPD.

  2. IBM
    Provides support for DPD encoded DFP values.

In some cases, distinct library distributions provide symbols with differing prefixes so as to avoid symbol collision.

The Intel library is included in the following open source packages:

  • Gcc (in libgcc/config/libbid; conditionally compiled into libgcc)
    License: GPLv3 with the version 3.1 GCC Runtime Library Exception
    This distribution includes implementations of gcc’s intrinsic functions (e.g., __bid_addsd3) as light wrappers around library functions (e.g., __bid32_to_bid64, __bid64_add, __bid64_to_bid32).

  • libdfp (in libbid; synchronized with gcc)
    License: GPLv3 with the version 3.1 GCC Runtime Library Exception

  • Intel® Decimal Floating-Point Math Library
    License: BSD (3-clause)

The IBM library is included in the following open source packages:

  • Gcc (in libdecnumber; conditionally compiled into libgcc)
    License: GPLv3 with the version 3.1 GCC Runtime Library Exception
    This distribution includes functions to convert between DPD and BID encoding.

  • libdfp (in libdecnumber; synchronized with gcc)
    License: GPLv3 with the version 3.1 GCC Runtime Library Exception

  • ICU (in icu4c/source/i18n, mixed in with other source files)
    License: ICU License – ICU 1.8.1 and later.
    This distribution contains a modified subset of the source files present in gcc and libdfp. Symbols are prefixed with uprv_.

The IBM library is also included in the base C library package for AIX.

Gcc includes symbols from both of the above libraries in its runtime support library (libgcc) depending on DFP enablement and target support.

When Clang is invoked to use libgcc as the compiler support library, it will be necessary to dispatch DFP operations to it. Alternatively, dispatch to a different library would be possible but would require synchronizing environment properties like, for example, the rounding mode that may be set and observed by the fe_dec_setround() and fe_dec_getround() functions respectively.

compiler-rt does not currently include DFP support.

Regardless of the compiler support library in use, it may be necessary to enable use of an alternate DFP support library; e.g., the libdfp or Intel® Decimal Floating-Point Math libraries linked above, or perhaps libmpdec (a library used by Python but not, to my knowledge, used to provide DFP support in any current C or C++ compilers). This would entail adding support for a -dfplib= option similar to the existing -rtlib= and -unwindlib= options.

Driver and compiler options:

Gcc does not, as far as I can tell, have driver or compiler options to enable or disable DFP support; DFP support is determined at build time via configure option --enable-decimal-float and/or the target. There are four modes:

  • no
    DFP support is not enabled.
    This is the default for targets other than x86, x86_64, Power, and z/Architecture.

  • yes
    DFP support is enabled, value representation is BID for x86 and x86_64 targets and DPD for all other targets.
    This is the default for most x86, x86_64, Power, and z/Architecture targets.

  • bid
    DFP support is enabled, BID is used for value representation.

  • dpd
    DFP support is enabled, DPD is used for value representation.

Since Clang is generally built to support multiple targets, gcc’s approach of enabling or disabling DFP support at build time is less applicable; Clang will need to enable or disable DFP support based on the selected target at translation time. This could lead to a situation in which the libgcc support library being used is built without DFP support, but the selected target is one for which DFP support is implicitly or explicitly enabled. This situation cannot, in general, be detected at compile-time, so such scenarios may result in mismatched configurations not being diagnosed until link time. This situation requires the ability to disable DFP support at compile-time so that compilation of libraries that conditionally utilize DFP features based on feature test macros like __STDC_IEC_60559_DFP__ can be successfully built.

AST and LLVM IR:

I expect AST and LLVM IR changes to be relatively straight forward and to follow the established patterns used for fixed-point support. Those patterns suggest:

  1. New BuiltinType kinds for the new DFP builtin types. The same BuiltinType would be used for both the C and C++ forms of the types (e.g., _Decimal32 and float __attribute__((mode(SD)))) would both map to, e.g., ASTContext::Decimal32Ty).
  2. New CastKind, BinaryOperatorKind, and UnaryOperatorKind kinds for DFP conversions and operations.
  3. New DecimalFloatingLiteral. This node will only appear in C since UDLs will be used in C++. An option can be added to enable the C DFP literal suffixes in C++ mode in the future if necessary.
  4. A new APDecimalFloat class similar to APFloat and APFixedPoint to be used to store and perform conversions and arithmetic operations for DFP types at translation time.
  5. New builtin functions to match those provided by gcc. Known builtins include __builtin_infd32(), __builtin_nansd32(), __builtin_nansd64(), and __builtin_nansd128() though there may be more. Additional builtins may be desired for to, e.g., construct a constant value from a string representation.
  6. Possibly new LLVM IR Type kinds, but that may prove to be unnecessary; such new types appear not to have been needed for fixed point support.
  7. New LLVM IR Intrinsic kinds; I’ll need to research this further.

Proposal:

The following describes directional intent but not necessarily an execution plan.

  1. Work with copyright owners to contribute appropriately licensed (ideally, Apache License v2.0 with LLVM Exceptions) versions of the Intel and IBM DFP libraries to compiler-rt modified to align with LLVM coding and style conventions.
  2. Provide support for both DFP libraries included in gcc’s run-time support library when -rtlib=libgcc is in effect.
  3. Provide support for both DFP libraries included in compiler-rt when -rtlib=compiler-rt is in effect.
  4. Add a new -dfplib= driver and compiler option to select a specific DFP support library with possible values of libgcc and compiler-rt. If present, this option will override the default DFP support library implied by -rtlib=. Future work can then implement support for other support libraries (e.g., the DFP support in AIX’s libc).
  5. Add new -fdecimal-floating-point and -fno-decimal-floating-point driver and compiler options modeled after -ffixed-point and -fno-fixed-point.
    1. Provide a diagnostic when an attempt is made to enable support for a target for which support has not yet been implemented.
    2. Delegate DFP enablement and selection of BID or DPD encoding to TargetInfo and classes that derive from it.
  6. Extend the Clang AST as outlined in the previous section.
  7. Extend diagnostics for formatted I/O functions to recognize the %H, %D, and %DD type specifiers.
  8. Extend the LLVM IR to support new DFP types and intrinsics following the established patterns used for fixed-point support (I’ll need to research this further).

Tom.

1 Like

Thank you for putting together this RFC! I really appreciate the details and that you’re looking into adding support for this to Clang. Overall, I heartily approve of the RFC.

I assume that in any situation where the TS 18661 behavior differs from what is rolled into C23, the standard behavior will “win” and we won’t worry about differentiating between the standard and the TS/TR?

Are you planning to make the types and facilities available in older language standard modes given that the type names are in the reserved namespace already?

Are you (or someone else) intending to do work to bring this interface to libc++ as well?

It’d be ideal if we could find a way to improve that situation, as I would expect it to cause some amount of user confusion. But I don’t see it being a blocking issue, either.

One thing you may want to explicitly budget time for is constant expression evaluation (you touched on it in the proposal a bit). I’m hopeful that much of the functionality will fall out naturally, but it’s an important use case that could be a time sink.

Some other tasks to think about:

  • Name mangling schemes (the Itanium manglings may already exist, but we should check on Microsoft), and other ABI considerations to make sure we are able to interoperate with other compilers.
  • What pieces and parts of this proposal should be available in freestanding mode (if any)? Related, are there headers which Clang provides as an implementation that need to be updated or added?
  • Do we need to change any existing functionality to exclude decimal floats or behave differently? e.g., pragmas that change the floating-point environment, that sort of thing.

Thank you for the comments, Aaron!

I assume that in any situation where the TS 18661 behavior differs from what is rolled into C23, the standard behavior will “win” and we won’t worry about differentiating between the standard and the TS/TR?

That is certainly my intention, yes. If we find some motivation for preferring behavior in the other direction, then we can evaluate whether it is worth adding an option.

Are you planning to make the types and facilities available in older language standard modes given that the type names are in the reserved namespace already?

I don’t see a reason not to at this point. As far as I can tell, gcc enables these features unconditionally (when it was built with such support). As mentioned, we’ll need an option to disable the feature anyway; I would expect that to suffice.

Are you (or someone else) intending to do work to bring this interface to libc++ as well?

I am happy to do it If time permits, but I haven’t evaluated how much effort would be involved. I think it isn’t that big, but famous last words and all that.

It’d be ideal if we could find a way to improve that situation, as I would expect it to cause some amount of user confusion. But I don’t see it being a blocking issue, either.

I don’t expect this to be much of an issue in practice. Gcc has supported the DFP extension for 10+ years, so I don’t expect to encounter many cases where we’ve opted to implicitly enable support for a given target, but then the libgcc being used lacks support.

One thing you may want to explicitly budget time for is constant expression evaluation (you touched on it in the proposal a bit). I’m hopeful that much of the functionality will fall out naturally, but it’s an important use case that could be a time sink.

Absolutely. This will effectively require a DFP implementation in Clang itself; that is what the APDecimalFloat class will be required for. I’ll likely end up hitting you up for more advice when I start getting to that point.

Thank you for mentioning name mangling; I did indeed forget about that. The Itanium ABI does define manglings for the builtin types (Df for _Decimal32, Dd for _Decimal64, and De for _Decimal128) and gcc does use those. Given that MSVC doesn’t support DFP yet, I’m guessing there is no mangling defined at this point. It may not make sense to enable DFP support for Windows at this point; I’ll have to research further.

I had not given any thought to freestanding support. That will require more research.

It is possible that some headers will require some updates. I haven’t given much thought to the standard library side yet as I am focused on the core language support right now. As far as I know, glibc does not implement the DFP library functions, but presumably will at some point. Coordination at that point may be required.

Do we need to change any existing functionality to exclude decimal floats or behave differently? e.g., pragmas that change the floating-point environment, that sort of thing.

Not that I’m aware of. I believe the DFP environment should be completely separate from the host FP environment.