Skip to Main Content Skip to Footer Navigation

Sorry, your browser is not supported. We recommend upgrading your browser. We have done our best to make all the documentation and resources available on old versions of Internet Explorer, but vector image support and the layout may not be optimal. Technical documentation is available as a PDF Download.

You copied the Doc URL to your clipboard.

C language extensions

Data types

This section overlaps with the specification of the Arm Procedure Call Standard, particularly [AAPCS] (4.1). ACLE extends some of the guarantees of C, allowing assumptions to be made in source code beyond those permitted by Standard C.

  • Plain char is unsigned, as specified in the ABI [AAPCS] and [AAPCS64] (7.1.1).
  • When pointers are 32 bits, the long type is 32 bits (ILP32 model).
  • When pointers are 64 bits, the long type may be either 64 bits (LP64 model) or 32 bits (LLP64 model).

ACLE extends C by providing some types not present in Standard C and defining how they are dealt with by the AAPCS.

Implementation-defined type properties

ACLE and the Arm ABI allow implementations some freedom in order to conform to long-standing conventions in various environments. It is suggested that implementations set suitable defaults for their environment but allow the default to be overridden.

The signedness of a plain int bit-field is implementation-defined.

Whether the underlying type of an enumeration is minimal or at least 32-bit, is implementation-defined. The predefined macro __ARM_SIZEOF_MINIMAL_ENUM should be defined as 1 or 4 according to the size of a minimal enumeration type such as enum { X=0 }. An implementation that conforms to the Arm ABI must reflect its choice in the Tag_ABI_enum_size build attribute.

wchar_t may be 2 or 4 bytes. The predefined macro __ARM_SIZEOF_WCHAR_T should be defined as the same number. An implementation that conforms to the Arm ABI must reflect its choice in the Tag_ABI_PCS_wchar_t build attribute.

Predefined macros

Several predefined macros are defined. Generally these define features of the Arm architecture being targeted, or how the C/C++ implementation uses the architecture. These macros are detailed in Feature test macros. All ACLE predefined macros start with the prefix __ARM.

Intrinsics

ACLE standardizes intrinsics to access the Arm ® Neon ™ architecture extension. These intrinsics are intended to be compatible with existing implementations. Before using the Neon intrinsics or data types, the <arm_neon.h> header must be included. The Neon intrinsics are defined in Advanced SIMD (Neon) intrinsics. Note that the Neon intrinsics and data types are in the user namespace.

ACLE standardizes intrinsics to access the Arm M-profile Vector Extension (MVE). These intrinsics are intended to be compatible with existing implementations. Before using the MVE intrinsics or data types, the <arm_mve.h> header must be included. The MVE intrinsics are defined in M-profile Vector Extension (MVE) intrinsics. Note that the MVE data types are in the user namespace, the MVE intrinsics can optionally be left out of the user namespace.

ACLE also standardizes other intrinsics to access Arm instructions which do not map directly to C operators generally either for optimal implementation of algorithms, or for accessing specialist system-level features. Intrinsics are defined further in various following sections.

Before using the non-Neon intrinsics, the <arm_acle.h> header should be included.

Whether intrinsics are macros, functions or built-in operators is unspecified. For example:

  • It is unspecified whether applying #undef to an intrinsic removes the name from visibility
  • It is unspecified whether it is possible to take the address of an intrinsic

However, each argument must be evaluated at most once. So this definition is acceptable:

#define __rev(x) __builtin_bswap32(x)

but this is not:

#define __rev(x) ((((x) & 0xff) << 24) | (((x) & 0xff00) << 8) | \
  (((x) & 0xff0000) >> 8) | ((x) >> 24))

Constant arguments to intrinsics

Some intrinsics may require arguments that are constant at compile-time, to supply data that is encoded into the immediate fields of an instruction. Typically, these intrinsics require an integral-constant-expression in a specified range, or sometimes a string literal. An implementation should produce a diagnostic if the argument does not meet the requirements.

Header files

<arm_acle.h> is provided to make the non-Neon intrinsics available. These intrinsics are in the C implementation namespace and begin with double underscores. It is unspecified whether they are available without the header being included. The __ARM_ACLE macro should be tested before including the header:

#ifdef __ARM_ACLE
#include <arm_acle.h>
#endif /* __ARM_ACLE */

<arm_neon.h> is provided to define the Neon intrinsics. As these intrinsics are in the user namespace, an implementation would not normally define them until the header is included. The __ARM_NEON macro should be tested before including the header:

#ifdef __ARM_NEON
#include <arm_neon.h>
#endif /* __ARM_NEON */

<arm_mve.h> is provided to define the M-Profile Vector Extension (MVE) intrinsics. By default these intrinsics occupy both the user namespace and the __arm_ namespace, defining __ARM_MVE_PRESERVE_USER_NAMESPACE will hide the definition of the user namespace variants. The __ARM_FEATURE_MVE macro should be tested before including the header:

::
#if (__ARM_FEATURE_MVE & 3) == 3 #include <arm_mve.h> /* MVE integer and floating point intrinsics are now available to use. / #elif __ARM_FEATURE_MVE & 1 #include <arm_mve.h> / MVE integer intrinsics are now available to use. */ #endif

<arm_fp16.h> is provided to define the scalar 16-bit floating point arithmetic intrinsics. As these intrinsics are in the user namespace, an implementation would not normally define them until the header is included. The __ARM_FEATURE_FP16_SCALAR_ARITHMETIC feature macro should be tested before including the header:

#ifdef __ARM_FEATURE_FP16_SCALAR_ARITHMETIC
#include <arm_fp16.h>
#endif /* __ARM_FEATURE_FP16_SCALAR_ARITHMETIC */

Including <arm_neon.h> will also cause <arm_fp16.h> to be included if appropriate.

These headers behave as standard library headers; repeated inclusion has no effect beyond the first include.

It is unspecified whether the ACLE headers include the standard headers <assert.h>, <stdint.h> or <inttypes.h>. However, the ACLE headers will not define the standard type names (for example uint32_t) except by inclusion of the standard headers. Programmers are recommended to include the standard headers explicitly if the associated types and macros are needed.

In C++, the following source code fragments are expected to work correctly:

#include <stdint.h>
// UINT64_C not defined here since we did not set __STDC_FORMAT_MACROS
...
#include <arm_neon.h>

and:

#include <arm_neon.h>
...
#define __STDC_FORMAT_MACROS
#include <stdint.h>
// ... UINT64_C is now defined

Attributes

GCC-style attributes are provided to annotate types, objects and functions with extra information, such as alignment. These attributes are defined in Attributes and pragmas.

Implementation strategies

An implementation may choose to define all the ACLE non-Neon intrinsics as true compiler intrinsics, i.e. built-in functions. The <arm_acle.h> header would then have no effect.

Alternatively, <arm_acle.h> could define the ACLE intrinsics in terms of already supported features of the implementation, for example compiler intrinsics with other names, or inline functions using inline assembler.

Half-precision floating-point

ACLE defines the __fp16 type, which can be used for half-precision (16-bit) floating-point in one of two formats. The binary16 format defined in [IEEE-FP], and referred to as IEEE format, and an alternative format, defined by Arm, which extends the range by removing support for infinities and NaNs, referred to as alternative format. Both formats are described in [ARMARM] (A2.7.4), [ARMARMv8] (A1.4.2).

Toolchains are not required to support the alternative format, and use of the alternative format precludes use of the ISO/IEC TS 18661:3 [CFP15] _Float16 type and the Armv8.2-A 16-bit floating-point extensions. For these reasons, Arm deprecates the use of the alternative format for half precision in ACLE.

The format in use can be selected at runtime but ACLE assumes it is fixed for the life of a program. If the __fp16 type is available, one of __ARM_FP16_FORMAT_IEEE and __ARM_FP16_FORMAT_ALTERNATIVE will be defined to indicate the format in use. An implementation conforming to the Arm ABI will set the Tag_ABI_FP_16bit_format build attribute.

The __fp16 type can be used in two ways; using the intrinsics ACLE defines when the Armv8.2-A 16-bit floating point extensions are available, and using the standard C operators. When using standard C operators, values of __fp16 type promote to (at least) float when used in arithmetic operations, in the same way that values of char or short types promote to int. There is no support for arithmetic directly on __fp16 values using standard C operators.

void add(__fp16 a, __fp16 b) {
  a + b; /* a and b are promoted to (at least) float.
            Operation takes place with (at least) 32-bit precision.  */
  vaddh_f16 (a, b); /* a and b are not promoted.
                       Operation takes place with 16-bit precision.  */
}

Armv8 introduces floating point instructions to convert 64-bit to 16-bit i.e. from double to __fp16. They are not available in earlier architectures, therefore have to rely on emulation libraries or a sequence of instructions to achieve the conversion.

Providing emulation libraries for half-precision floating point conversions when not implemented in hardware is implementation-defined.

double xd;
__fp16 xs = (float)xd;

rather than:

double xd;
__fp16 xs = xd;

In some older implementations, __fp16 cannot be used as an argument or result type, though it can be used as a field in a structure passed as an argument or result, or passed via a pointer. The predefined macro __ARM_FP16_ARGS should be defined if __fp16 can be used as an argument and result. C++ name mangling is Dh as defined in [cxxabi], and is the same for both the IEEE and alternative formats.

In this example, the floating-point addition is done in single (32-bit) precision:

void add(__fp16 *z, __fp16 const *x, __fp16 const *y, int n) {
   int i;
   for (i = 0; i < n; ++i) z[i] = x[i] + y[i];
 }

Relationship between __fp16 and ISO/IEC TS 18661

ISO/IEC TS 18661-3 [CFP15] is a published extension to [C11] which describes a language binding for the [IEEE-FP] standard for floating point arithmetic. This language binding introduces a mapping to an unlimited number of interchange and extended floating-point types, on which binary arithmetic is well defined. These types are of the form _FloatN, where N gives size in bits of the type.

One instantiation of the interchange types introduced by [CFP15] is the _Float16 type. ACLE defines the __fp16 type as a storage and interchange only format, on which arithmetic operations are defined to first promote to a type with at least the range and precision of float.

This has implications for the result of operations which would result in rounding had the operation taken place in a native 16-bit type. As software may rely on this behaviour for correctness, arithmetic operations on __fp16 are defined to promote even when the Armv8.2-A fp16 extension is available.

Arm recommends that portable software is written to use the _Float16 type defined in [CFP15].

Type conversion between a value of type __fp16 and a value of type _Float16 leaves the object representation of the converted value unchanged.

When __ARM_FP16_FORMAT_IEEE == 1, this has no effect on the value of the object. However, as the representation of certain values has a different meaning when using the Arm alternative format for 16-bit floating point values [ARMARM] (A2.7.4) [ARMARMv8] (A1.4.2), when __ARM_FP16_FORMAT_ALTERNATIVE == 1 the type conversion may introduce or remove infinity or NaN representations.

Arm recommends that software implementations warn on type conversions between __fp16 and _Float16 if __ARM_FP16_FORMAT_ALTERNATIVE == 1.

In an arithmetic operation where one operand is of __fp16 type and the other is of _Float16 type, the _Float16 type is first converted to __fp16 type following the rules above, and then the operation is completed as if both operands were of __fp16 type.

[CFP15] and [C11] do not define vector types, however many C implementations do provide these extensions. Where they exist, type conversion between a value of type vector of __fp16 and a value of type vector of _Float16 leaves the object representation of the converted value unchanged.

ACLE does not define vector of _Float16 types.

Was this page helpful? Yes No