July 22, 2024·4 min read·engineeringcglibcc23bits

glibc 2.40 carries bit precision through the generic interface

Wider stdbit.h operands now use compiler builtins when available. The change illustrates why C23 support depends on the compiler and system headers working together.

A type-generic operation loses its purpose if its implementation first narrows the value to a familiar integer width. That becomes a practical concern when the compiler accepts integers wider than unsigned long long. The glibc 2.40 release on 22 July changes <stdbit.h> generic macros to use the new standard-operation builtins with GCC 14.1 or later. The result is support for unsigned extended and bit-precise operands when the target supports those types.

I value C interfaces that make representation choices inspectable. A generic bit operation should preserve the representation the caller selected. This release reduces the need for an application to maintain a second collection of helpers merely because an internal integer became wider.

The compiler knows the operand precision

The 2.40 header shows how the implementation selects the new path. For stdc_leading_zeros, it checks whether the compiler has __builtin_stdc_leading_zeros. If so, the generic macro forwards its argument to that builtin. Otherwise, the header retains the previous implementation based on the unsigned long long function and a width adjustment.

This is a capability check in the header. The announcement describes the supported GCC release, while the source tests the operation the compiler exposes. The distinction is useful when understanding how compiler and library releases interact. Installing a library header cannot teach an older compiler a new builtin, and accepting a new integer type does not establish support for every operation over it.

The earlier glibc 2.39 header makes the limitation visible. Its generic leading-zero calculation starts with the widest conventional unsigned function and corrects for a narrower input. That technique handles the supported conventional widths. A type wider than the intermediate cannot be accommodated by subtracting a width difference after conversion. Information lost during conversion is no longer available to count.

Forwarding to a builtin gives the compiler the original operand type. That permits it to define the count in terms of the actual precision. The application can retain a single operation name across different internal representations. This is particularly useful for code where the width is a deliberate algorithm parameter, because a local cast to a smaller type would change the question the operation answers.

Generic support does not widen every function

The release still declares explicitly suffixed functions for the standard unsigned types. The new generic path does not change the parameter of stdc_leading_zeros_ull into an arbitrary-width parameter. A call to that function continues to express an unsigned long long operation. The widened support belongs to the type-generic interface.

This matters when a program has wrappers around the original functions. A wrapper whose parameter is unsigned long long has already chosen its input representation. Changing the implementation inside that wrapper to a generic macro cannot recover a wider caller's original type. The wrapper contract needs to preserve the type information if that information is necessary to the calculation.

There is a similar distinction between supported types and enabled declarations. A compiler can recognize a bit-precise type on one target while another target has different support. The release announcement explicitly qualifies arbitrary precision with target support. A portable component should therefore describe the representation it requires and the environments in which that requirement is satisfied.

This is why a generic spelling is useful without being a universal portability guarantee. It removes one source-level distinction for callers on supported toolchains. It does not establish identical language facilities on every compiler and machine. A project still needs to identify the combination that implements its chosen representation.

C23 has a named library request

The release adds _ISOC23_SOURCE as a feature-test macro and retains _ISOC2X_SOURCE. It also enables the supported C23 facilities through _GNU_SOURCE or the C23 compiler modes named in the announcement. The new spelling lets a source file request the relevant library facilities using the standard's settled name.

The release features header documents the macro's purpose. It distinguishes user requests from internal macros that system headers use to select declarations. Application code should express the requested environment through the documented public request. Internal implementation macros are part of how glibc carries that request between headers.

For build configuration, this introduces a useful separation. The language mode determines which source constructs the compiler accepts. A feature-test request determines which supported library declarations headers expose. Those decisions meet in one translation unit, but they have different responsibilities. A standard-mode name is therefore insufficient evidence of every library facility a project intends to call.

I prefer describing support in terms of those specific requirements. A project can require a particular header operation and operand type without claiming complete implementation of C23. That makes a portability failure easier to interpret, because it points to the facility actually missing.

The same release expands the mathematical library

The announcement also adds the C23 exponential families exp2m1 and exp10m1, together with logarithmic families log2p1, log10p1 and logp1. It identifies their earlier origin in TS 18661-4:2015 and describes variants for the supported floating types, plus generic macros in <tgmath.h>. These additions show that standard adoption includes library work independent of the parser's recognition of new syntax.

For an application author, the common engineering issue is availability across the selected toolchain. A bit operation implemented through a compiler builtin and a mathematical function supplied by the library reach the source file through different mechanisms. Both may belong to the same standard revision. Their implementation requirements should still be checked separately when defining a supported build environment.

The bit-operation change is a concrete example of that cooperation. The library provides the public macro and retains its fallback path. On the supported target, the compiler supplies an operation capable of interpreting the original unsigned precision. Together they let the caller preserve its chosen type through the calculation, without converting the value into a narrower conventional representation first.