N2965
Modern Bit Utilities

Published Proposal,

Previous Revisions:
n2903 (r1), n2827 (r0)
Author:
Latest:
https://thephd.dev/_vendor/future_cxx/papers/C - Moder Bit Utilities.html
Paper Source:
github.com/ThePhD/future_cxx
Issue Tracking:
GitHub
Proposal Category:
Feature Request
Target:
C23
Project:
ISO/IEC JTC1/SC22/WG14 9899: Programming Language — C

Abstract

Endian preprocessor macros, byte swapping, big endian / little endian load and store, functions, and several bit utilities that have become commonplace amongst compilers, bytecodes, and implementations.

1. Changelog

1.1. Revision 2 - April 12th, 2022

1.2. Revision 1 - January 1st, 2022

1.3. Revision 0 - October 15th, 2021

2. Polls

These polls help guide the design of this paper in accordance with WG14 consensus. Where consensus was not sufficient or close (or there were many abstentions in conjunction with not having much consensus), the author chose in a particular direction and provided rationale.

2.1. WG14 Virtual Meeting - February 2022

WG14 reviewed an earlier version of this paper in N2903, discussing many of its design choices and aspects. WG14 was asked about which functions from the given set below to keep in the paper or remove: all sets of functions were approved when asking the 5 questions about which functionality should be kept (answered questions were moved to the Appendix in § 6.1 Decisions to Committee Questions). This was interpreted as unanimous consent to proceed with all of the functionality in this paper. If there is anyone who is interested in bisecting or taking pieces apart from this proposal, please let the authors know as soon as is humanly possible.

2.1.1. Does WG14 want the memreverse8 and endian load/store functions to only be required if CHAR_BIT == 8 similar to N2903?

Yes No Abstain
6 5 8

This was interpreted as not strong enough consensus, but it was left to the author to decide. As we do not want to leave freestanding implementations which have CHAR_BIT == 16 or CHAR_BIT == 32 out in the cold, we decided to leave the CHAR_BIT % 8 == 0 mandate in, rather than switch to CHAR_BIT == 8 as a stringent constraint. This complicates the specification but makes the functionality more widely available.

One of the suggestions that came from doing this would also be to require the generic bit functions to take a parameter indicating the desired final width of the integer result, that the user would then cast. This is seen currently in the standard in functions such as fromfp, ufromfp, and similar found in the C Standard in §7.12.9.10 and §7.12.9.11. Unfortunately, this is less justifiable because existing practice does not follow this pattern for any bit intrinsics. Bit intrinsics are deeply tied to the width of the object being computed and the assumption of that width is what produces the most optimal code since it maps 1:1 with instruction sets and hardware sets. Better code generation can be achieved by providing a width parameter that is a constant (e.g., (unsigned int)stdc_leading_zeros(value, UINT_WIDTH) or similar). The only problem with this is when either (a) weaker compilers that do not do any constant propagation or expression computation beyond the very minimal set required by the C compiler, or (b) the width parameter is not a constant value by necessity or accident.

For example, MISRA C and CERT discourage #define constants without strong justification. This is due to unbounded scoping issues endemic to the preprocessor (for macro constants). Similarly, using "magic numbers" (unnamed constants) is non-compliant. Trying to use const int width = UINT_WIDTH; is also discouraged as it - and other constant expressions stored in const or even const static variables - may or may not optimize to a constant (it is strictly not a constant expression, as determined by C’s abstract machine rules; see: CERT C DCL06). Using enumerations may solve this problem partially for MISRA C/Safety Standard Compliance, but this is an awful lot of effort for what should be straightforward code generation on even low-quality, non-optimizing implementations. (Recognizing a standard function and providing a builtin for it is existing practice, even on compilers who barely afford to do optimizations such as "tinycc". Propagating constant expressions into function calls, standard or not, is less so existing practice from available implementations whose source code can be inspected.)

We also do not have existing practice for bit functions that are specified in this way. These functions are usually meant to map to a tight set of hardware instructions, and are meant to be cheaply translatable to said hardware instructions. So, we focus on providing things that map directly to standard and extended unsigned integer types as well as bit-precise integers that match exact-width integer types. This proposal does not spend further time explore providing width as a parameter. We think that this may be a good idea in the future, but this is something we should allow for implementations to provide for.

2.1.2. Does WG14 want new signed-count rotate functions in addition to what is in N2903?

Yes No Abstain
8 6 6

This was interpreted as very close consensus, and also left to the author to decide. However, it was made clear in post-discussion that the current design for rotate left/right is fine, because it is a symmetrical operation, and is completely free to implement on 2’s complement implementations. Another important factor in making this decision was noting that most compilers already generate optimal code with a signed count value, including x86_64, x32_64, i686, AARCH64 (Arm 64-bit), and Arm 32-bit targets. Finally, there are architectures were both rotate left and rotate right instructions are available, but they do not have the same performance characteristics: the end-user should be able to use either rotate_left or rotate_right to bias the implementation towards a given instruction where possible.

2.1.3. Does WG14 want to put something along the lines of N2903 into C23?

Yes No Abstain
19 2 2

This is very clear direction to put it into C23, provided that the wording and other design details are hammered into place. We are working on these details.

3. Introduction & Motivation

There is a lot of proposals and work that goes into figuring out the "byte order" of integer values that occupy more than 1 octet (8 bits). This is nominally important when dealing with data that comes over network interfaces and is read from files, where the data can be laid out in various orders of octets for 2-, 3-, 4-, 6-, or 8-tuples of octets. The most well-known endian structures on existing architectures include "Big Endian", where the least significant bit comes "last" and is featured prominently in network protocols and file protocols; and, "Little Endian", where the least significant bit comes "first" and is typically the orientation of data for processor and user architectures most prevalent today.

In more legacy architectures (Honeywell, PDP), there also exists other orientations called "mixed" or "middle" endian. The uses of such endianness are of dubious benefit and are vanishingly rare amongst commodity and readily available hardware today, but nevertheless still represent an applicable ordering of octets.

In other related programming interfaces, the C functions/macros ntoh ("network to host") and hton ("host to network") (usually suffixed with l or ul or others to specify which native data type it was being performed on such as long) were used to change the byte order of a value ([ntohl]). This became such a common operation that many compilers - among them Clang and GCC - optimized the code down to use an intrinsic __builtin_bytewap(...)/__builtin_bswap(...) (for MSVC, for Clang, and for GCC). These intrinsics often compiled into binary code representing cheap, simple, and fast byte swapping instructions available on many CPUs for 16, 32, 64, and sometimes 128 bit numbers. The bswap/byteswap intrinsics were used as the fundamental underpinning for the ntoh and hton functions, where a check for the translation-time endianness of the program determined if the byte order would be flipped or not.

This proposal puts forth the fundamentals that make a homegrown implementation of htonl, ntoh, and other endianness-based functions possible in Standard C code. It also addresses many of the compiler-based intrinsics found to generate efficient machine code, with a few simpler utilities layered on top of it.

3.1. Bits: How Much Faster?

Just how much faster can using intrinsics and bit operations as proposed in this paper be? Below is a quantification of the performance differences from naïve algorithms that worked over one "bit" (or bool) at a time by attempting to implement a few algorithms using it. The explanations of these graphs can be found at one of the publicly available implementation of this code in its documentation - https://ztdidk.readthedocs.io/en/latest/benchmarks/bit.html.

If you don’t read the previous link, then at the very least it should be shown that the code describes in this proposal provides the means to implement the improvements shown in the ztdc_packed group of benchmark bars.

4. Design

This is a library addition. It is meant to expose both macros and functions that can be used for translation time-suitable checks. It provides a way to check endianness within the preprocessor, and gives definitive names that allow for knowing whether the endianness is big, little, or neither. We state big, little, or neither, because there is no settled-upon name for the legacy endianness of "middle" or "mixed", nor any agreed upon ordering for such a "middle" or "mixed" endianness between architectures. This is not the case for big endian or little endian, where one is simply the reverse of the other, always, in every case, across architectures, file protocols, and network specifications.

The next part of the design is functions for working with groupings of 8 bits. They are meant to communicate with network or file protocols and formats that have become ubiquitous in computing for the last 30 years.

This design also provides a small but essential suite of bit utilities, all within the #include <stdbit.h> header.

4.1. Preliminary: Why the stdc_ prefix?

We use the stdc_ prefix for these functions so that we do not have to struggle with taking common words away from the end user. Because we now have 31 bytes of linker name significance, we can afford to have some sort of prefix rather than spend all of our time carving out reserved words or header-specific extensions. This will let us have good names that very clearly map to industry practice, without replacing industry code or being forced to be compatible with existing code that already has taken the name with sometimes-conflicting argument conventions.

4.2. Charter: unsigned char const ptr[static sizeof(uintN_t)] and More?

There are 2 choices on how to represent sized pointer arguments. The first is a void* ptr convention for functions arguments in this proposal. The second is an unsigned char ptr[static n]/unsigned char ptr[sizeof(uintN_t)] convention.

To start, we still put any size + ptr arguments in the proper "size first, pointer second" configuration so that implementation extensions which allow void [static n] can exist no matter what choice is made here. That part does not change. The void* argument convention mean that pointers to structures, or similar, can be passed to these functions without needing a cast. This represents the totality of the ease of use argument. The unsigned char ptr[static n] argument convention can produce both better compile-time safety and articulate requirements using purely the function declaration, without needing to look up prose from the C Standard or implementation documentation. The cost is that any use of the function will require a cast in strictly conforming code.

One of the tipping arguments in favor of our choice of unsigned char ptr[static n] is that void* can be dangerous, especially since we still do not have a nullptr constant in the language and 0 can be used for both the size and the pointer argument. (Which is, very sadly, an actual bug that happens in existing code. Especially when users mix memset and memcpy calls and use the wrong 0 argument because of writing one and meaning the other, and copying values over a large part of their 0-pointer in their low-level driver code.) Using an unsigned char* (or its statically-sized array function argument form) means that usage of the functions below would require explicit casting on the part of the user. This is, in fact, the way it is presented in [portable-endianness]: as far as existing practice is concerned, users of the code would rather cast and preserve safety rather than easily use something like stdc_memreverse8 with the guts of their structure.

4.3. Signed vs. Unsigned

This paper has gone back and forth between signed vs. unsigned count offsets for the rotl/rotr instruction-based functions, and similarly the return types for many of the types which return purely a "count"-style value. Some important properties and facts follow:

This brings up a lot of questions about whether or not the functions here should be signed or unsigned. We will analyze this primarily from the standpoint of rotate_left and rotate_right, as that has the greatest impacts for the portability and semantics of the code presented here.

4.3.1. In Defense of Signed Integers

Let us consider a universe where stdc_rotate_left and friends take a signed count. This allows negative numbers to be passed to the count value for the rotate left. So, when stdc_rotate_leftuc(1, -1) is called, it will call itself again with stdc_rotate_rightuc(value, -count); if (e.g.) stdc_rotate_rightuc(1, -1) is called, it will call itself again with stdc_rotate_leftuc(value, -count). This is because, specification-wise, these functions are symmetric and cyclical in what they are meant to do. This matches the behavior from C++ and avoids undefined behavior for negative numbers, while also avoiding too-large shift errors from signed-to-unsigned conversions.

SDCC and several other compilers optimize for left and right shifts ([sdcc]). Texas Instruments and a handful of other specialist architectures also have "variable shift" instructions (SSHVL), which uses the sign of the argument to shift in one direction or the other ([ti-tms320c64x]). Having a rotate_left where the a negative number produces the opposite rotate_right cyclic operation (and vice-versa) means that both of these architectures can optimize efficiently in the case of hardcoded constants, and still produce well-defined behavior otherwise (SSHVL instructions just deploy a "negated by default" for the count value or not, depending on whether the left or right variant is called, other architectures propagate the information to shift left or right). This also follows existing practice with analogous functions from the C++ standard library.

To test code generation for using a signed integer and 2’s complement arithmetic, we used both C++ and C code samples. It’s a fairly accurate predictor of how notable compilers handle this kind of specification. The generated assembly for the compilers turns out to be optimal, so long as an implementation does not do a literal copy-paste of the specification’s text

Using non-constant offset, with generated x86_64 assembly:

#include <bit>

extern unsigned int x;
extern int offset;

int main () {
    int l = std::rotl(x, offset);
    int r = std::rotr(x, offset);
    return l + r;
}
main: # @main
	mov     eax, dword ptr [rip + x]
	mov     cl, byte ptr [rip + offset]
	mov     edx, eax
	rol     edx, cl
	ror     eax, cl
	add     eax, edx
	ret

— And, using constant offset, with generated x86_64 assembly.

#include <bit>

extern unsigned int x;

int main () {
    int l = std::rotl(x, -13);
    int r = std::rotr(x, -13);
    return l + r;
}
main: # @main
	mov     eax, dword ptr [rip + x]
	mov     ecx, eax
	rol     ecx, 19
	rol     eax, 13
	add     eax, ecx
	ret

The generated code shows that the compiler understands the symmetric nature of the operations (from the constant code) and also shows that it will appropriately handle it even when it cannot see through constant values. The same can be shown when writing C code using a variety of styles, as shown here:

#if UNSIGNED_COUNT == 1

static unsigned int rotate_right(unsigned int value, unsigned int count);

inline static unsigned int rotate_left(unsigned int value, unsigned int count) {
	unsigned int c = count % 32;
	return value >> c 
		| value << (32 - c);
}

inline static unsigned int rotate_right(unsigned int value, unsigned int count) {
	unsigned int c = count % 32;
	return value << c
		| value >> (32 - c);
}

#elif TWOS_COMPLEMENT_CAST == 1

static unsigned int rotate_right(unsigned int value, int count);

inline static unsigned int rotate_left(unsigned int value, int count) {
	unsigned int c = (unsigned int)count;
	c = c % 32;
	return value >> c
		| value << (32 - c);
}

inline static unsigned int rotate_right(unsigned int value, int count) {
	unsigned int c = (unsigned int)count;
	c = c % 32;
	return value << c
		| value >> (32 - c);
}

#else

static unsigned int rotate_right(unsigned int value, int count);

inline static unsigned int rotate_left(unsigned int value, int count) {
	int c = count % 32;
	if (c < 0)  {
		return rotate_right(value, -c);
	}
	return value >> c
		| value << (32 - c);
}

	inline static unsigned int rotate_right(unsigned int value, int count) {
	int c = count % 32;
	if (c < 0)  {
		return rotate_left(value, -c);
	}
	return value << c
		| value >> (32 - c);
}

#endif

#if UNSIGNED_COUNT == 1
unsigned int f (unsigned int x, unsigned int offset) {
#else
unsigned int f (unsigned int x, int offset) {
#endif
	unsigned int l = rotate_left(x, offset);
	unsigned int r = rotate_right(x, offset);
	return l + r;
}

When using the various definitions, we find that the generated assembly for f is identically good using either the internal unsigned "two’s complement" cast, or by just using an unsigned number. Because of how poorly basic mathematics with unsigned numbers happens, we want to avoid a situation where negation or subtraction with unsigned qualities may yield undesirable results or promotions. Therefore, we used signed integers for both the offset count and the return values of these functions. Note that even in purely standard C, converting from a signed integer to an unsigned integer is perfectly well-defined behavior and does not raise any signals:

2   Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

— §6.3.1.3, ¶2, ISO/IEC 9899:202x "C2x" Standard

Finally, the vast majority of existing practice takes the offset value in as a signed integer, and all the return types are also still some form of signed integer (unless the intrinsic is returning the exact same unsigned value put in that was manipulated). It also allows "plain math" being done on the type to naturally manifest negative numbers without accidentaly having roundtripping or signed/unsigned conversion issues.

4.3.2. In Defense of Unsigned

Unsigned, on the other hand, has existing practice in hardware. While the intrinsics defined by glibc, C++'s standard libraries, and many more use signed integers, they are conceptually unsigned in their implementations. For example, for a 32-bit rotate, most standard libraries taking an int offset parameter perform:

count = count & 31;

This is critical for optimization here. Note that, if we were to provide a specification using a signed offset, our specification has to very deliberately specify that we are going to negate the value and then pass it to the rotate of the opposite direction. This is, effectively, the same as obliterating the sign value and then calling the (symmetrical, cyclical) rotate: a 32-bit rotate therefore can get identical codegen as a signed variant by using the a bit AND (NOT a normal % 32, as that preserves the sign as we do NOT want that). For an unsigned variant, no such trickery is necessary. Simply truncating the value using:

count = count % 32;

produces optimal code generation for most compilers, as they understand that bit AND for hexadecimal 0x1F (decimal 31) is identical to modulus of decimal 32. This means that, by default, unsigned values are the same here. Abusing 2’s complement, one can save this by simply doing unsigned u_count = (unsigned)count; and then perform modulus to get the same behavior as performing bit AND with 31. The "obvious" code is the efficient code here, as shown by the example of the assembly above.

Rust is one of the few languages that provides optimal versions of this code using unsigned. Their code is optimal under both optimizations and a lack thereof, compared to C and C++ code which struggles with function call elision and similar. This may be aided in the future by having this paper put into the C standard, which would allow compilers to treat standard-specific rotate calls as intrinsics to be replaced with the instructions directly.

All in all, unsigned naturally optimizes better and matches the size type of C. It has no undefined behavior on overflow and produces better assembly in-general when it comes to bit intrinsics. Shifting behavior is also well-defined for unsigned types and not signed types, further compounding unsigned types as far better than their signed counterparts.

4.3.3. Which Does This Paper Choose?

Ultimately, this paper chooses signed integer types. This is primarily to satisfy architectures which have signed-based variably style shifts. These platforms would have to convert to signed values to perform their variable shifts either way, so it benefits them. We also know that, for 2’s complement architectures, signed can be treated best by simply deploying count & 31 as a way to produce a truncated absolute value.

Furthermore, existing practice in C uses signed integer types for the count for rotate_left (and it’s analogous builtins and similar). Nominally, breaking with existing practice is actually not difficult in this case because the behavior for the rotate is, once again, done as-if it’s an unsigned value that can rotate in any direction. However, it is important to remember that the use of positive or negative values can influence the direction of the rotate, as well as the choice of which function.

I expect this decision will not be extremelty popular. Ultimately, I expect to poll this at the next meeting. Whichever direction gets higher consensus, will be the direction I pursue for this functionality.

4.4. The __STDC_ENDIAN_* Macros

The enumeration is specified as follows:

#include <stdbit.h>

#define __STDC_ENDIAN_LITTLE__ /* some unique value */
#define __STDC_ENDIAN_BIG__ /* some other unique value */
#define __STDC_ENDIAN_NATIVE__ /* see below! */

The goal of these macros is that if the system identifies as a "little endian" system, then __STDC_ENDIAN_LITTLE__ == __STDC_ENDIAN_NATIVE__, and that is how an end-user knows that the implementation is little endian. Similarly, a user can check __STDC_ENDIAN_BIG__ == __STDC_ENDIAN_NATIVE__, and they can know the implementation is big endian. Finally, if the system is neither big nor little endian, than __STDC_ENDIAN_NATIVE__ is a unique value that does not compare equal to either value:

#include <stdbit.h>
#include <stdio.h>

int main () {
	if (__STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_LITTLE__) {
		printf("little endian! uwu\n");
	}
	else if (__STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_BIG__) {
		printf("big endian OwO!\n");
	}
	else {
		printf("what is this?!\n");
	}
	return 0;
}

If a user has a Honeywell architecture or a PDP architecture, it is up to them to figure out which flavor of "middle endian"/"mixed endian"/"bi endian" they are utilizing. We do not give these a name in the set of macros because neither the Honeywell or PDP communities ever figured out which flavor of the 32-bit byte order of 2341/3412/2143/etc. was strongly assigned to which name ("mixed" endian? "mixed-big" endian? "bi-little" endian?), and since this is not a settled matter in existing practice we do not provide a name for it in the C Standard. It is also of dubious determination what the byte order for a 3-byte, 5-byte, 6-byte, or 7-byte integer is in these mixed-endian types, whereas both big and little have dependable orderings.

4.4.1. A (Brief) Discussion of Endianness

There is a LOT of design space and deployed existing practice in the endianness space of both architectures and their instruction sets. A non-exhaustive list of behaviors is as follows:

Suffice to say, there exists a lot of deployed practice. Note that this list effectively has these concerns in priority order. The first is the most conventional software; as the list goes down, each occurrence becomes more rare and less interesting. Therefore, we try not to spend too much time focusing on what are effectively the edge cases of software and hardware. Some of the past choices in endianness and similar were simply due "going with the flow" (PDP’s "2143" order) or severe historical baggage (early FORTRAN dealing in big endian floating point numbers, and those algorithms and serialization methods being given to PDP machines without thinking about the ordering). With much of the industry moving away from such modes in both newer mainframes and architectures and towards newer implementations and architectures, it does not seem prudent to try to standardize the multitude of their behaviors.

This proposal constraints its definition of endianness to integer types without padding, strictly because trying to capture the vast berth of existing architectures and their practices can quickly devolve down a slope that deeply convolutes this proposal’s core mission: endian and bit utilities.

4.4.2. Hey! Some Architectures Can Change Their Endianness at Run-time!

This is beyond the scope of this proposal. This is meant to capture the translation-time endianness. There also does not appear to be any operating system written today that can tolerate an endianness change of the whole program happening arbitrarily at runtime, after a program has launched. This means that the property is effectively a translation-time property, and therefore can be exposed as a compile-time constant. A future proposal to determine the run-time byte order is more than welcome from someone who has suitable experience dealing with such architectures and programs, and this proposal does not preclude their ability to provide such a run-time function e.g. stdc_endian get_execution_endian(void);.

Certain instruction sets have ways to set the endianness of registers, to change how data is accessed ([arm-setend]). This functionality is covered by byte swapping, and byte swaps can be implemented using the SETEND instruction plus an access. (The compiler would have to remember to unwind the endian state back to its original value, however, or risk contaminating the entire program and breaking things.)

4.4.3. Floating Point has a Byte Order, Too.

For the design of this paper, we strictly consider the design space for (unsigned) integers, only. Floating point numbers already have an implementation-defined byte order, and none of these functions are meant to interact with the floating point types. While the stdc_memreverse8 function can work on any memory region, which includes any structure, scalar, or similar type with or without padding bits, the function just swaps bytes. Nothing needs to be said about padding bits in this case, since the operation is well-defined in all cases.

It shall be noted that for C++, since C++20, its endian enumeration applies to all scalar types:

This subclause describes the endianness of the scalar types of the execution environment.

— C++ Standard Working Draft, bit.endian/p1

It does not specify what this means for padding bits or similar; nor, I think, does it have to. Byte order means very little for padding bits until serialization comes into play. C++ does not define any functions which do byte-order aware serialization. So, it does not have to write any specification governing what may or may not happen and the left is rest undefined / unspecified.

For this proposal, we focus purely on integer types and, more specifically, on integer types which do not have padding or where we can work with a padding bits-agnostic representation. While it is acknowledged that floating point types and pointers have byte orders too, we do not want to interact directly with these types when it comes to endianness load and store functions. Byte swaps, (bit) population counts, and other bit operations can be performed on floating point types after they have been copied or type-punned (with implementation checking/blessing) into equivalent (unsigned) integer objects to do the necessary work.

4.5. Generic 8-bit Memory Reverse and Exact-width 8-bit Memory Reverse

In order to accommodate both a wide variety of architectures but also support minimum-width integer optimized intrinsics, this proposal takes from the industry 2 forms of byteswap:

These end up inhabiting the stdbit.h header and have the following interface:

#include <stdbit.h>
#include <limits.h>
#include <stdint.h>

#if (CHAR_BIT % 8 == 0)
void stdc_memreverse8(size_t n, unsigned char ptr[static n]);
uintN_t stdc_memreverse8uN(uintN_t value);
#endif

where N is one of the minimum-width integer types such as 8, 24, 16, 32, 64, 128, and others. On most architectures, this matches the builtins (MSVC, Clang, GCC) and the result of compiler optimizations that produce instructions for many existing architectures as shown in the README of this portable endianness function implementation. We use the exact-width values for the uN-suffixed functions because we expect that C compilers would want to lower the stdc_memreverse8uN call to existing practice of byteswapN instructions and compiler intrinsics. Using uint_leastN_t reduces the ability to match these existing optimizations in the case where uintN_t functions are not defined.

One property of note is that memreverse8 swaps 8 bits at a time rather than CHAR_BIT bits at a time (this is why it has the suffix "8" in the name). This matches existing practice: all known byteswap operations work on 8 bits. This caveat is here because we need to retain cross-platform behavior. If we swapped to using CHAR_BIT, then the behavior of a program that uses no implementation-defined properties would suddenly become dependent on implementation/architecture properties:

// NOT guaranteed, if it works on CHAR_BIT
// instead of working on 8 bits at a time.
assert(stdc_memreverse8u32(0xAABBCCDD) == 0xDDCCBBAA);

One of the problems with this approach is that it opens us up to potentially having padding bits if CHAR_BIT is not a multiple of 8. There are a number of approaches to this, but the ultimate reality is that it is simply not portable using any other definition. If the goal is standard functions and the purpose of these types is to create a way to talk to other processors (or different kinds of cores all along the same bus), files in specific formats, or networks, then we have to stick to using an 8-bit byte and not letting unspecified amounts of padding filtering into the representation. This also allows the code, when present, to map reasonably to available intrinsics: note that even the GCC builtins work explicitly on 8-bit-bytes, no matter the platform. We are simply following existing practice, here.

4.5.1. But Memory Reverse Is Dangerous?

Byte swapping, by itself, is absolutely dangerous in terms of code portability. Users often program strictly for their own architecture when doing serialization, and do not take into consideration that their endianness can change. This means that, while stdc_memreverse functions can compile down to intrinsics, those intrinsics get employed to change "little endian" to "big endian" without performing the necessary "am I already in the right endianness" check. Values that are already in the proper byte order for their target serialization get swapped, resulting in an incorrect byte order for the target network protocol, file format, or other binary serialization target.

The inclusion of the <stdbit.h> header reduces this problem by giving access to the __STDC_NATIVE_ENDIAN__ macro definition, but does not fully eliminate it. This is why many Linux and BSDs include functions which directly transcribe from one endianness to another. This is why the Byte Order Fallacy has spread so far in Systems Programming communities, and why many create their own versions of this both in official widespread vendor code ([linux-endian]) and in more personal code used for specific distributions ([portable-endianness]). Thusly, this proposal includes some endianness functions, specified just below.

4.6. stdc_load8_*/stdc_store8_* Endian-Aware Functions

Functions meant to transport bytes to a specific endianness need 3 pieces of information:

To represent any operation that goes from/to the byte order that things like long longs are kept in, the Linux/BSD/etc. APIs use the term "host", represented by h. Every other operation is represented by explicitly naming it, particularly as be or le for "big endian" or "little endian". Again, because of the severe confusion that comes from what the exact byte order a "mixed endian" multi byte scalar is meant to be in, there seems not to exist any widely available practice regarding what to call a PDP/Honeywell endian configuration. Therefore, mixed/bi/middle-endian is not included in this proposal. It can be added at a later date if the community ever settles on a well-defined naming convention that can be shared between codebases, standards, and industries.

The specification for the endianness functions borrows from many different sources listed above, and is as follows:

#include <stdbit.h>
#include <limits.h>
#include <stdint.h>

#if ((N % CHAR_BIT) == 0 && (CHAR_BIT % 8 == 0))
void stdc_store8_leuN(uint_leastN_t value,
	unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_beuN(uint_leastN_t value,
	unsigned char ptr[static (N / CHAR_BIT)]);
uint_leastN_t stdc_load8_leuN(
	const unsigned char ptr[static (N / CHAR_BIT)]);
uint_leastN_t stdc_load8_beuN(
	const unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_aligned_leuN(uint_leastN_t value,
	unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_aligned_beuN(uint_leastN_t value,
	unsigned char ptr[static (N / CHAR_BIT)]);
uint_leastN_t stdc_load8_aligned_leuN(
	const unsigned char ptr[static (N / CHAR_BIT)]);
uint_leastN_t stdc_load8_aligned_beuN(
	const unsigned char ptr[static (N / CHAR_BIT)]);

void stdc_store8_lesN(int_leastN_t value,
	unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_besN(int_leastN_t value,
	unsigned char ptr[static (N / CHAR_BIT)]);
int_leastN_t stdc_load8_lesN(
	const unsigned char ptr[static (N / CHAR_BIT)]);
int_leastN_t stdc_load8_besN(
	const unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_aligned_lesN(int_leastN_t value,
	unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_aligned_besN(int_leastN_t value,
	unsigned char ptr[static (N / CHAR_BIT)]);
int_leastN_t stdc_load8_aligned_lesN(
	const unsigned char ptr[static (N / CHAR_BIT)]);
int_leastN_t stdc_load8_aligned_besN(
	const unsigned char ptr[static (N / CHAR_BIT)]);
#endif

Thanks to some feedback from implementers and librarians, this first implementation would also need an added signed variant to the load and store functions as well as aligned and unaligned loads and stores. While C23 will mandate a two’s complement representation for integers, because we are using the uint_leastN_t functions (which may be larger than the intended N == 24 or N == 32 specification), it is important for the sign bit to be properly serialized an transported. Therefore, during stdc_load8_(le/be)sN/stdc_load8_(le/be)uN operations, the sign bit will be directly serialized into resulting signed value or byte array where necessary.

This specification is marginally more complicated than the stdc_memreverseuN functions because they operate on uint_leastN_t, where N is the minimum-width bit value. These functions, on most normal implementations, will just fill in the exact number of 8, 16, 32, 64, etc. bits. But for Digital Signal Processors (DSPs), select embedded architectures, and many freestanding implementations, it is impossible to offer a CHAR_BIT == 8 guarantee. For example, some Digital Signal Processors have CHAR_BIT == 32, and all of uint_least8_t, uint_least16_t, uint_least24_t, and uint_least32_t are all aliased to the same fundamental type.

We are fine with not making these precisely uintN_t/intN_t because the upcoming C23 Standard includes a specific allowance that if uintN_t/intN_t exist, then uint_leastN_t/int_leastN_t must match their exact-width counterparts exactly, which has been existing practice on almost all implementations for quite some time now.

Similarly to stdc_memreverse8, we want a dependable set of functionality that can work across platforms. Therefore, the functions only exist if both N and CHAR_BIT is evenly divisible by 8. We use the (u)int_leastN_t types still because we want these functions to be generally available when the requirements are met, because we can guarantee a proper value as long as a user is working with (u)int_leastN_t as anticipated. A lack of padding bits is not required to work with the memory correctly, unlike stdc_memreverse8 and its exact-width counterpart.

Note that this means a CHAR_BIT == 16 implementation can still implement a stdc_load8_les24 function, as it satisfies both ((CHAR_BIT % 8) == 0) and ((N % 8) == 0) and uses the int_least24_t parameter, which is guaranteed to be available in that implementation’s <stdint.h> header.

4.7. Modern Bit Utilities

Additionally to this, upon first pre_review of the paper there was a strong supporting groundswell for bit operations that have long been present in both hardware and as compiler intrinsics. This idea progressed naturally from the bswap and __builtin_bswap discussion. As indicated in [p0553] (merged into C++20 already), here’s a basic rundown of some common architectures and their support for various bit functionality:

operation Intel/AMD ARM PowerPC
rotl ROL - rldicl
rotr ROR ROR, EXTR -
popcount POPCNT - popcntb
leading_zero BSR, LZCNT CLZ cntlzd
leading_one - CLS -
trailing_zero BSF, TZCNT - -
trailing_one - - -

Many of the below bit functions are defined below to ease portability to these architectures. For places where specific compiler idioms and automatic detection are not possible, similar assembly tricks or optimized implementations can be provided by C. Further bit functions were also merged into C++, resulting in the current state of the C++ bit header.

There is further a bit of an "infamous" page amongst computer scientists for Bit Twiddling Hacks. These may not all map directly to instructions but they provide a broad set of useful functionality commonly found in not only CPU-based programming libraries, but GPU-based programming libraries and other high performance computing resources as well.

We try to take the most useful subset of these functions that most closely represent functionality on both old and new CPU architectures as well as common, necessary operations that have been around in the last 25 years for various industries. We have left out operations such as sign extension, parity computation, bit merging, clear/setting bits, fast negation, bit swaps, lexicographic next bit permutation, and bit interleaving. The rest are more common and appear across a wide range of industries from cryptography to graphics to simulation to efficient property lookup and kernel scheduling.

4.7.1. "Why not only generic interfaces or (u)intmax_t interfaces?"

For many of the bit-based utilities, you will see it introduces functions with several suffixes for the various types. Often, it is asked: why? Even the GCC builtins for things like popcount only take long and long long. The answer is in the blank spaces in the table above: for architectures that do not have perfect instruction mappings for a given built-in type (e.g., ARM for popcount), the amount of bits one is utilizing for the given function is actually incredibly important. There is a difference between counting for 8 bits in a loop and counting 64 bits (or larger for extended integer types), so the various forms are provided to allow implementations to produce the most efficient code on their platforms when the user requests a specific size.

The generic interfaces can be used by individuals who want automatic selection of the best. And, as shown in the § 6 Appendix, platforms can use any builtins or techniques at their disposal to select an appropriate built-in, instruction, or function call to fit the use case.

4.7.2. Type-Generic Macros and Counts for Types

All of the functions below have type generic macros associated with them. This can bring up an interesting question: if the return value depends on the type of the argument going into the function (i.e. for trailing_zeros, trailing_ones, leading_zeros, leading_ones, rotate_left, and rotate_right), is it bad for literal arguments? The answer to this question, however, is the same as its always been when dealing with literal values in C: use the suffix for the appropriate type, or cast, or put it in a const variable so that it can be used with the expected semantics. We cannot sink macro-based generic code use cases in the off-chance that someone calls stdc_trailing_zeros(0) and thinks it returns a dependable answers. Integers (and their literals) are the least portable part of Standard C code: use the exact-width types if you are expecting exact-width semantics. Or, call the fundamental-type suffixed versions to get answers dependable for that given type (e.g., stdc_trailing_zerosui(0)).

4.7.3. Argument Types

Many of the functions below are defined over the fundamental unsigned integer types, rather than their minimum width or exact width counterparts. This is done to provide maximum portability: users can combine information from the recently-introduced (S/U)CHAR/(U)SHRT/(U)INT/(U)LONG/(U)LLONG_WIDTH macros to determine the width of the sizes at translation time as well as enjoy a disjoint and distinct set of fundamental types over which generic selection will always work.

The (u)int_leastN_t types also have WIDTH macros, but those macros are not exactly guaranteed to cover a wide range of actual bit sizes either (if the uintN_t types do not exist, then a conforming implementation can simply just name all of the types as typedefs for (unsigned) long long and call it a day). While an implementation could also define each of the distinct fundamental types from (unsigned) char to (unsigned) long long to all be the same width as well, we are at the very least guaranteed that they are, in fact, distinct types. This makes selection over types in _Generic predictable and usable (i.e. _Generic(x, uint_least32_t: 0, uint_least64_t: 1) is not guaranteed to compile since those types are not required to form a mutually exclusive or disjoint set).

The exact-width types suffer from non-availability on specific platforms, which makes little sense for functions which do not depend on a no-padding bits requirement. As long as the values read from the array only involve N bits (including the sign bit), and the rest are zero-initialized, we can have predictable semantics.

Extended integer types, least-width integer types, and exact-width integer types, can all be used with the type-generic macros since the type-generic macros are required to work over all standard (unsigned) integer types and extended (unsigned) integer types, while excluding bool and bit-precise (_BitInt(N)) integer types that do not match pre-existing type widths. This provides a complete set of functionality that is maximally portable while also allowing for precise semantic control with exact or least-width types.

This paper does not concern itself with the implications of passing a uint_leastN_t to bit-counting type-generic functions like stdc_leading_zeros directly: a user must account for such use and be prepared to have types larger than N bits in width. This is, very literally, what users are signing up for when they use such types and it is their responsibility to query the UINT_LEASTN_WIDTH macros. We expect users to use the N of their exact-width integer types with the type-generic macros as well.

Finally, in general bool objects are disallowed from the above functions. There is just not a meaningful body of functionality that can be provided, and there is a fundamental difference between something that is expected to be a boolean value and something that is expected to be a 1-bit number (even if they can both serve similar purposes). It is also questionable to compute things such as rotation for bool objects. If we can grow a consistent set of answers for these operations across the industry, than we can weaken the requirements and add the behavior in. (Note that if we put it in now and choose a behavior, we cut off any improvements made in the future, so it is best to be conservative here.)

4.7.4. Return Types

There is the question of what is meant to happen for types which return bit counts, such as stdc_(leading/trailing)_(ones/zeros), stdc_first_(leading/trailing)_(one/zero), and stdc_count_(ones/zeros). Ostensibly, part of the motivation to capture here should be that the types used to do things such as rotations should be identical to the return type used to do things like count zeros, e.g. stdc_rotate_left(value, stdc_count_zeros(value));. This is mostly non-problematic until someone uses _BitInt: Clang already supports several megabyte-large _BitInt. On platforms where int is actually 16 bits, this is far too small to accommodate even a 1 MB _BitInt.

At the moment, the functions do not accept all bit-precise integer types (just ones that are bit-width equivalent to the existing standard and extended integer types), so this is technically a non-issue. But, if and when bit-precise integer types are given better handling in _Generic macros or similar features that make them more suitable for type-generic macro implementations, this could become a problem. At the moment, we use wording to defer the issue by saying that type generic macros return a type suitably large for the range of the computed value. This allows us forward compatibility while fixing non-type-generic macro return types to int. The type-generic macros will have the flexibility from the specification to return larger signed integer types to aid in a smooth transition once bit-precise integer types sees more standard support.

4.7.5. stdc_count_ones/stdc_count_zeros

stdc_count_ones (also known as popcount/Population Count) is an older computer science term taken from the statistics / biology nomenclature to indicate how many bits are set within a grouping. It’s a very useful instruction with applications in everything from game development to scientific computing. It is also directly provided by many instruction sets. Its antithesis is stdc_count_zeros, which counts the number of zeros in the type. There exist efficient computation, intrinsics, and instructions for both zeros and ones computation, albeit it is more prevalent as popcount. We chose the name stdc_count_zeros and stdc_count_ones due to not having a good way to describe the zeros-analogous version of popcount in industry-settled terminology. But, the count_zeros/count_ones split has been used to good success in C libraries, C++ libraries, Julia, Rust, and other (standard) libraries.

The API for it is as such:

#include <stdbit.h>

int stdc_count_onesuc(unsigned char value);
int stdc_count_onesus(unsigned short value);
int stdc_count_onesui(unsigned int value);
int stdc_count_onesul(unsigned long value);
int stdc_count_onesull(unsigned long long value);

int stdc_count_zerosuc(unsigned char value);
int stdc_count_zerosus(unsigned short value);
int stdc_count_zerosui(unsigned int value);
int stdc_count_zerosul(unsigned long value);
int stdc_count_zerosull(unsigned long long value);

// type-generic macros
generic_return_type stdc_count_ones(
	generic_value_type value);
generic_return_type stdc_count_zeros(
	generic_value_type value);

It covers all of the built-in unsigned integer types. The type-generic macro supports all of the built-in types as well as any of the implementation-defined extended integer types. See the appendix for an implementation.

4.7.6. stdc_rotate_left/stdc_rotate_right

stdc_rotate_left/stdc_rotate_right are common CPU instructions and the forms of the commonly-used circular shifts. They are common operations with applications in cyclic codes. They are commonly expressed (for 32-bit numbers) as value << count | value >> (32 - count) (rotate left) or value >> count | value << (32 - count) (rotate right).

#include <stdbit.h>

unsigned char stdc_rotate_leftuc(unsigned char value, int count);
unsigned short stdc_rotate_leftus(unsigned short value, int count);
unsigned int stdc_rotate_leftui(unsigned int value, int count);
unsigned long stdc_rotate_leftul(unsigned long value, int count);
unsigned long long stdc_rotate_leftull(unsigned long long value, int count);

unsigned char stdc_rotate_rightuc(unsigned char value, int count);
unsigned short stdc_rotate_rightus(unsigned short value, int count);
unsigned int stdc_rotate_rightui(unsigned int value, int count);
unsigned long stdc_rotate_rightul(unsigned long value, int count);
unsigned long long stdc_rotate_rightull(unsigned long long value, int count);

// type-generic macro
generic_value_type stdc_rotate_left(
	generic_value_type value, generic_count_type count);
generic_value_type stdc_rotate_right(
	generic_value_type value, generic_count_type count);

They cover all of the built-in unsigned integer types. A discussion of signed vs. unsigned integer types for the count type and the return type can be found in a previous section, here § 4.3 Signed vs. Unsigned.

As for choosing a single function like stdc_rotate(unsigned-integer-type value, int count); that chooses left / right based on the value, it unfortunately imposes the worst code generation properties of all the options. When using entirely runtime values, unless you have a deliberately have a variable-rotate/shift instruction, you are requireed to emit a branch in order to handle the two cases, as rotate left / right - despite being symmetric - need some help. Here is the assembly for a tehcnically optimal left/right rotate:

f:	# @f
	mov     r8d, edi
	mov     ecx, esi
	rol     r8d, cl
	mov     edx, edi
	ror     edx, cl
	mov     ecx, esi
	neg     ecx
	mov     eax, edi
	rol     eax, cl
	ror     edi, cl
	test    esi, esi
	cmovs   edx, r8d
	cmovle  eax, edi
	add     eax, edx
	ret

This is more than double the size of the rotates found using left/right directly in § 4.3 Signed vs. Unsigned. Due to this, we decided that it was not advantageous to have a signed count with an unknown left/right: it is important to be capable of biasing the optimizer to whether a given rotate is left/right oriented.

4.7.7. stdc_leading_zeros, stdc_leading_ones, stdc_trailing_zeros, and stdc_trailing_ones

stdc_leading_zeros, stdc_leading_ones, stdc_trailing_zeros, and stdc_trailing_zeros are semi-common CPU instruction for counting the number of zeros/ones from the most significant bit ("leading") and the least significant bit ("trailing"). C++ adopted this one using the names of the form count(l|r)_(zero|one). The l/r stand for "left" and "right". C++ uses left to match the concept of the left hand side of integers in lexical parsing and left shift operators in C an C++. We choose "leading" and "trailing" here as that’s the more common instruction name, and tie in a little bit better with "most/least significant bit" than "left" or "right" do. The name most_significant_zeros (and its variations for the other 3 operations) can also work, albeit it would be one of the biggest names in the C standard library if we do choose it. (This could potentially be shortened to most_signif_zeros or even most_sig_zeros). It may also run afoul of the 31 minimum linker bytes of significance we have, so we chose these names instead.

#include <stdbit.h>

int stdc_leading_zerosuc(unsigned char value);
int stdc_leading_zerosus(unsigned short value);
int stdc_leading_zerosui(unsigned int value);
int stdc_leading_zerosul(unsigned long value);
int stdc_leading_zerosull(unsigned long long value);

int stdc_leading_onesuc(unsigned char value);
int stdc_leading_onesus(unsigned short value);
int stdc_leading_onesui(unsigned int value);
int stdc_leading_onesul(unsigned long value);
int stdc_leading_onesull(unsigned long long value);

int stdc_trailing_zerosuc(unsigned char value);
int stdc_trailing_zerosus(unsigned short value);
int stdc_trailing_zerosui(unsigned int value);
int stdc_trailing_zerosul(unsigned long value);
int stdc_trailing_zerosull(unsigned long long value);

int stdc_trailing_onesuc(unsigned char value);
int stdc_trailing_onesus(unsigned short value);
int stdc_trailing_onesui(unsigned int value);
int stdc_trailing_onesul(unsigned long value);
int stdc_trailing_onesull(unsigned long long value);

// type-generic macros
generic_return_type stdc_leading_zeros(
	generic_value_type value);
generic_return_type stdc_leading_ones(
	generic_value_type value);
generic_return_type stdc_trailing_zeros(
	generic_value_type value);
generic_return_type stdc_trailing_ones(
	generic_value_type value);

4.7.8. stdc_first_leading_zero, stdc_first_leading_one, stdc_first_trailing_zero, and stdc_first_trailing_one

stdc_first_leading_zero, stdc_first_leading_one, stdc_first_trailing_zero, and stdc_first_trailing_zero are semi-common CPU instruction (bsf/bsr for Intel, bfffo for Motorola, ffs for VAX, and so on) for counting the number of zeros/ones from the most significant bit ("leading") and the least significant bit ("trailing"). The caveat here is that it produces the bit index plus one. There are a few compiler-based implementations of this. The first is MSVC’s _BitScanForward and _BitScanReverse (with 64 prefix for 64-bit versions). They are meant to mimic Intel’s instruction behavior where a flag is set if "0" is passed, which is returned to the user who called the _BitScan* function. The actual output is populated in an output pointer variable of type int*. Notably, MSVC does not offer any ISA protection: it will emit an illegal CPU instruction if the target architecture doesn’t support the functionality. The other implementations are from Clang, GCC and NVIDIA CUDA, which have a compiler intrinsic which is then mapped to instructions where possible. They returns 0 when the input value is zero.

We specify things to use the interpretation that 0 produces the return value 0 and otherwise returns 1 + index. This interpretation is favorable because it allows an end-user to easily check the return value in a way consistent with typical C boolean checking, which is with if (result) { ... }. If result is zero, than the user knows it’s zero and knows no bit was found. Otherwise, they can proceed and subtract 1 to get the index suitable for shifts. If a user has advanced knowledge, they can simply not branch and immediately subtract.

ffs and its similar names covers the behavior behind stdc_first_leading_one. The others are permutations on this behavior: we provide them for completeness, and for the fact that other architectures cover some or part of these other named operations. Whatever happens, stdc_first_leading_one is incredibly important, if only for the fact that it is responsible for significant speedups in algorithms that scan over bits to find certain behaviors. The others can be built out of different the other existing intrinsics or with specially-crafted code, but not taxing the compiler’s optimize and simply providing the operations directly may be of great benefit.

It is of note that users can implement the find_first_set functionality by using the stdc_(trailing/leading)_zeros functions.

#include <stdbit.h>

int stdc_first_leading_zerouc(unsigned char value);
int stdc_first_leading_zerous(unsigned short value);
int stdc_first_leading_zeroui(unsigned int value);
int stdc_first_leading_zeroul(unsigned long value);
int stdc_first_leading_zeroull(unsigned long long value);

int stdc_first_leading_oneuc(unsigned char value);
int stdc_first_leading_oneus(unsigned short value);
int stdc_first_leading_oneui(unsigned int value);
int stdc_first_leading_oneul(unsigned long value);
int stdc_first_leading_oneull(unsigned long long value);

int stdc_first_trailing_zerouc(unsigned char value);
int stdc_first_trailing_zerous(unsigned short value);
int stdc_first_trailing_zeroui(unsigned int value);
int stdc_first_trailing_zeroul(unsigned long value);
int stdc_first_trailing_zeroull(unsigned long long value);

int stdc_first_trailing_oneuc(unsigned char value);
int stdc_first_trailing_oneus(unsigned short value);
int stdc_first_trailing_oneui(unsigned int value);
int stdc_first_trailing_oneul(unsigned long value);
int stdc_first_trailing_oneull(unsigned long long value);

// type-generic macros
generic_return_type stdc_first_leading_zero(
	generic_value_type value);
generic_return_type stdc_first_leading_one(
	generic_value_type value);
generic_return_type stdc_first_trailing_zero(
	generic_value_type value);
generic_return_type stdc_first_trailing_one(
	generic_value_type value);

4.7.9. stdc_has_single_bit

This is a function that determines if an unsigned integer is a power of 2. It can be written either using a normal expression such as value != 0 && ((value & (value - 1)) == 0), or by using stdc_count_ones(value) == 1. Checking that something is a power of 2 (or that it has a single bit set) is an operation used for checking if something can be turned into a mask value efficiently (useful in specific kinds of containers which specific bit limits like hash tables) and many other applications. This one does not map directly to a hardware instruction.

#include <stdbit.h>

bool stdc_has_single_bituc(unsigned char value);
bool stdc_has_single_bitus(unsigned short value);
bool stdc_has_single_bitui(unsigned int value);
bool stdc_has_single_bitul(unsigned long value);
bool stdc_has_single_bitull(unsigned long long value);

// type-generic macro
bool stdc_has_single_bit(generic_value_type value);

4.7.10. stdc_bit_width/stdc_bit_ceil/stdc_bit_floor

These set of functions provide a way to determine the number of bits it takes to represent a given value (bit_width), the next largest power of 2 from the value (bit_ceil), the previous largest power of 2 from the value (bit_floor), and the number of bits required to store the given value. All of these operations are extremely useful, especially in the context of GPUs. bit_width can be used to drastically simplify the implementation of both bit_ceil and bit_floor.

bit_width can be calculated with VALUE_WIDTH - stdc_leading_zeros(value), where VALUE_WIDTH is one of the <limits.h> macros for the given unsigned integer type. bit_ceil's computation is subtle and involves a bit of preparation to avoid problems with integer promotions and bit shifts in specific cases (typically unsigned char, char, and unsigned short on most implementations). This aids in making the case for a would make for a good candidate for standardization (since it can be hard to get right). One can detect integer promotion by checking if +x and x yield the same type. If not, then an integer promotion happens, and the implementation needs to account for that. See the appendix for an implementation. stdc_bit_floor is simpler, and is comprised of a simple computation of x == 0 ? 0 : (1 << (stdc_bit_width(x) - 1)) (with appropriately typed / casted constants so the right type is returned without promotions or casts).

The declarations look as follows:

#include <stdbit.h>

unsigned char stdc_bit_flooruc(unsigned char value);
unsigned short stdc_bit_floorus(unsigned short value);
unsigned int stdc_bit_floorui(unsigned int value);
unsigned long stdc_bit_floorul(unsigned long value);
unsigned long long stdc_bit_floorull(unsigned long long value);

unsigned char stdc_bit_ceiluc(unsigned char value);
unsigned short stdc_bit_ceilus(unsigned short value);
unsigned int stdc_bit_ceilui(unsigned int value);
unsigned long stdc_bit_ceilul(unsigned long value);
unsigned long long stdc_bit_ceilull(unsigned long long value);

int stdc_bit_widthuc(unsigned char value);
int stdc_bit_widthus(unsigned short value);
int stdc_bit_widthui(unsigned int value);
int stdc_bit_widthul(unsigned long value);
int stdc_bit_widthull(unsigned long long value);

// type-generic macro
generic_return_type stdc_bit_floor(generic_value_type value);
generic_return_type stdc_bit_ceil(generic_value_type value);
generic_return_type stdc_bit_width(generic_value_type value);

Notably, stdc_bit_width requires that the number is big enough to fit the representation. For the generic functions, we need to provide the built-in versions. Conceivably, it might be beneficial to synchronize these return types and just return int. But, in the case of something like an implementation for _BitInt(N), N can be so catastrophically enormous that we could not count it in a (presumably 16 or 32-bit) int or unsigned int type. C++ always returns the type T that was put in, but following a WG21 Library Working Group (LWG #3656) Issue accepted for C++23, the return type is being changed. However, in anticipation of a potentially enormous N in _BitWidth(N) — and not wanting to return an e.g. 4 GB _BitInt to represent a _BitWidth that has an N of 4 billion — we allow the return type for the generic functions to be a "suitably large (unsigned/signed) integer type".

5. Wording

The following wording is relative to N2731. For the rotate functions, wording is attached for all permutations of the polls taken, which are listed just below.

5.1. Decisions for the C Standards Committee

These are decisions the Committee might want to make to alter the wording below. Alternative wording is provided to guide the discussion and to make voting with the actual alternative specification in front of people’s eyes easier.

5.1.1. Question 0

— Given the new information present in the paper, do we want a single UnsignedType stdc_rotate(UnsignedType value, int count); function or two different stdc_rotate_left(UnsignedType value, int count); and stdc_rotate_right(UnsignedType value, int count); functions?

NOTE: #3 from § 5.1.2 Question 1 does not apply if this question is accepted, because then the rotate must have a sign to communicate left/right.

If the answer to this question is "Yes", then the below sections on "§7.✨.14 Rotate Left" and "§7.✨.15 Rotate Right" will be swapped out for the following wording:

7.✨.14 Rotate
Synopsis
unsigned char stdc_rotate_leftuc(unsigned char value, int count);
unsigned short stdc_rotate_leftus(unsigned short value, int count);
unsigned int stdc_rotate_leftui(unsigned int value, int count);
unsigned long stdc_rotate_leftul(unsigned long value, int count);
unsigned long long stdc_rotate_leftull(unsigned long long value, int count);

generic_value_type stdc_rotate_left(
	generic_value_type value, generic_count_type count);
Description
The stdc_rotate functions perform a bitwise rotate left or right. This operation is typically known as a left or right circular shift.
Returns
Let N be the width corresponding to the type of the input value. Let r be count % N.

— If r is 0, returns value;

— otherwise, if r is positive, returns (value << r) | (value >> (N - r));

— otherwise, if r is negative, returns (value >> -r) | (value << (N - -r)).

The type-generic function (marked by its generic_value_type argument) returns the above described result for a given input value so long as the generic_value_type is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

5.1.2. Question 1

— Do we want unsigned (unsigned int, and similar) rotate counts + return values? (Both the function parameters for counts and the count-like return value types will be changed to be consistent with this decision).

If the answer to this question is "Yes", then the following mechanical changes are made to the wording:

  1. The return types for the following functions is changed:

    • stdc_count_ones (and all derivatives) from int to unsigned int

    • stdc_count_zeros (and all derivatives) from int to unsigned int

    • stdc_leading_ones (and all derivatives) from int to unsigned int

    • stdc_leading_zeros (and all derivatives) from int to unsigned int

    • stdc_trailing_ones (and all derivatives) from int to unsigned int

    • stdc_trailing_zeros (and all derivatives) from int to unsigned int

    • stdc_first_leading_one (and all derivatives) from int to unsigned int

    • stdc_first_leading_zero (and all derivatives) from int to unsigned int

    • stdc_first_trailing_one (and all derivatives) from int to unsigned int

    • stdc_first_trailing_zero (and all derivatives) from int to unsigned int

    • stdc_bit_width (and all derivatives) from int to unsigned int

  2. Replace all instances of the following text:

    "The generic_return_type type for the type-generic function need not be the same as the type of value. It shall be suitably large signed integer type capable of representing the computed result."

    … with …

    "The generic_return_type type for the type-generic function need not be the same as the type of value. It shall be suitably large unsigned integer type capable of representing the computed result."

  3. Make the following modifications to the stdc_rotate_left and stdc_rotate_right functions:

    • Replace the parameter type for all the rotate functions from int count to unsigned int count for the second parameter.

    • Remove the bullet point for when a negative count/"r" is encountered: — otherwise, if r is negative, returns .

    • Change the last sentence for both functions concerning the types of the generic count and returns from:

      generic_count_type and generic_return_type type shall be suitably large signed integer types capable of representing the width of the generic_value_type and the computed result, respectively.

      … to …

      generic_count_type and generic_return_type type shall be suitably large signed integer types capable of representing the width of the generic_value_type and the computed result, respectively.

NOTE: #3 does not apply if § 5.1.1 Question 0 is accepted, because then the rotate must have a sign. This is captured in the wording shown above.

5.1.3. Question 2

There is also 1 more question that has been consistently asked of me as I’ve moved this proposal forward: changing how the suffixes for the types is done. Rather than doing uc, us, ui, ul, and ull, users have asked for _uc, _us, _ui, _ul, and _ull. This question is strictly for renaming the suffixes to have that additional underscore, for example going from stdc_leading_zerosull to stdc_leading_zeros_ull.

— Do we want to change the suffixes of all of the type-specific functions to use an underscore before the suffix?

5.2. Add <stdbit.h> to freestanding headers in §4, paragraph 6

A conforming freestanding implementation shall accept any strictly conforming program in which the use of the features specified in the library clause (Clause 7) is confined to the contents of the standard headers <float.h>, <iso646.h>, <limits.h>, <stdalign.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, <stdint.h>, <stdbit.h>, and <stdnoreturn.h>

5.3. Add a new bullet point at the top for globally-reserved macro and library names to §7.1.3 "Reserved Identifiers, paragraph 1.

— All identifiers starting with stdc_ are reserved for future use.

5.4. Add a new §7.✨ sub-clause for "Bit and Byte Utilities" in §7

7.✨ Bit and Byte Utilities <stdbit.h>

The header <stdbit.h> defines the following macros, types, and functions, to work with the byte and bit representation of many types, typically integer types. This header makes available the size_t type name (7.19) and any uintN_t,intN_t, uint_leastN_t, or int_leastN_t type names defined by the implementation (7.20).

5.4.1. Add a new §7.✨.1 sub-sub-clause for "Endian" in §7.✨

7.✨.1 Endian

Two common methods of byte ordering in multi-byte scalar types are big-endian and little-endian. Big-endian is a format for storage of binary data in which the least significant byte is placed first, with the rest in ascending order. Little-endian is a format for storage or transmission of binary data in which the most significant byte is placed first, with the rest in descending order. Other byte orderings are also possible. For declarations and definitions in 7.✨, an identifier with a suffix containing le typically represents little-endian. An identifier with a suffix containing be typically represents big-endian. This clause describes the endianness of the execution environment with respect to bit-precise integer types, standard integer types, and extended integer types which do not have padding bits.

It is unspecified whether any generic function declared in <stdbit.h> is a macro or an identifier declared with external linkage. If a macro definition is suppressed in order to access an actual function, or a program defines an external identifier with the name of a generic function, the behavior is unspecified.
The macros are:
__STDC_ENDIAN_LITTLE__

which represents a method of byte order storage least significant byte is placed first and the rest are in ascending order, and is an integer constant expression;

__STDC_ENDIAN_BIG__

which represents a method of byte order storage most significant byte is placed first and the rest are in descending order, and is an integer constant expression;

__STDC_ENDIAN_NATIVE__ /* see below */

which represents the method of byte order storage for the execution environment and is an integer constant expression.

__STDC_ENDIAN_NATIVE__ shall expand to an integer constant expression whose value is equivalent to the value of __STDC_ENDIAN_LITTLE__ if the execution environment is little-endian. Otherwise, __STDC_ENDIAN_NATIVE__ shall expand to an integer constant expression whose value is equivalent to the value of __STDC_ENDIAN_BIG__ if the execution environment is big-endian. If __STDC_ENDIAN_NATIVE__ is not equivalent to either, then the byte order for the execution environment is implementation-defined.

5.4.2. Add a new §7.✨.2 sub-sub-clause for "8-bit Memory Reversal" in §7.✨

7.✨.2 8-bit Memory Reversal
Synopsis
#include <stdbit.h>
#include <limit.h>

#if (CHAR_BIT % 8) == 0
void stdc_memreverse8(size_t n, unsigned char ptr[static n]);
#endif
Description

The stdc_memreverse8 function provides an interface to reverse the order of a given sequence of bytes by treating them as sequences of 8 bits at a time. The function is only present if CHAR_BIT is a multiple of 8. It is equivalent to the following algorithm:

for (size_t top_bit_index = 0, limit = (n * CHAR_BIT) / 2; top_bit_index < limit;) {
	const size_t index = top_bit_index / CHAR_BIT;
	const size_t reverse_index = n - 1 - index;
	unsigned char* p = ptr + index;
	unsigned char* reverse_p = ptr + reverse_index;
	const unsigned char b_temp = *p;
	const unsigned char reverse_b_temp = *reverse_p;
	*p = 0;
	*reverse_p = 0;
	for (size_t bit_index = 0; bit_index < CHAR_BIT; bit_index += 8) {
		const size_t reverse_bit_index = CHAR_BIT - 8 - bit_index;
		const unsigned char bit_mask = 0xFF << bit_index;
		const unsigned char reverse_bit_mask = 0xFF << reverse_bit_index;
		*p |= ((reverse_b_temp & reverse_bit_mask) << bit_index);
		*reverse_p |= ((b_temp & bit_mask) << reverse_bit_index);
		top_bit_index += 8
	}
}
7.✨.3 Exact-width 8-bit Memory Reversal
Synopsis
#include <stdbit.h>
#include <limits.h>
#include <stdint.h>

#if ((N % 8) == 0) && ((CHAR_BIT % 8) == 0)
uintN_t stdc_memreverse8uN(uintN_t value);
#endif
Description

The stdc_memreverse8uN functions provide an interface to swap the bytes of a corresponding uintN_t object, where N matches one of the exact-width integer types (7.20.1.1). If an implementation provides the corresponding uintN_t typedef, it shall define the corresponding exact-width memory reversal function for that value of N.

Returns

The stdc_memreverse8uN functions returns the 8-bit memory reversed uintN_t value, as if by invoking stdc_memreverse8(sizeof(value), (unsigned char*)&value).

5.4.3. Add a new §7.✨.4 sub-sub-clause for "Endian Aware" functions in §7.✨

7.✨.4 Endian-Aware 8-bit Load
Synopsis
#include <stdbit.h>

#if ((N % 8) == 0) && ((CHAR_BIT % 8) == 0)
uint_leastN_t stdc_load8_leuN(const unsigned char ptr[static (N / CHAR_BIT)]);
uint_leastN_t stdc_load8_beuN(const unsigned char ptr[static (N / CHAR_BIT)]);
uint_leastN_t stdc_load8_aligned_leuN(const unsigned char ptr[static (N / CHAR_BIT)]);
uint_leastN_t stdc_load8_aligned_beuN(const unsigned char ptr[static (N / CHAR_BIT)]);

int_leastN_t stdc_load8_lesN(const unsigned char ptr[static (N / CHAR_BIT)]);
int_leastN_t stdc_load8_besN(const unsigned char ptr[static (N / CHAR_BIT)]);
int_leastN_t stdc_load8_aligned_lesN(const unsigned char ptr[static (N / CHAR_BIT)]);
int_leastN_t stdc_load8_aligned_besN(const unsigned char ptr[static (N / CHAR_BIT)]);
#endif
Description

The 8-bit load family of functions functions read an int_leastN_t or uint_leastN_t object from the provided ptr in an endian-aware (7.✨.1) manner, where N matches an existing minimum-width integer type (7.20.1.2). If this function is present, N shall be a multiple of 8 and CHAR_BIT shall be a multiple of 8. The functions containing _aligned in the name shall assume that ptr is suitably aligned to access a signed or unsigned integer of width N for a signed or unsigned variant of the function, respectively. If the function name contains the sN suffix in the name, it is a signed variant. Otherwise, the function is an unsigned variant. If the function name contains the lesN or leuN suffix, it is a little-endian variant. Otherwise, if the function name contains the besN or beuN suffix, it is a big-endian variant.

Returns

Let value be an object of either int_leastN_t if the function is a signed variant or uint_leastN_t if the function is an unsigned variant initialized to 0. Let index be an integer in a sequence that

— starts from 0 and increments by 8 in the range of [0, N), if the function is a little endian variant;

— or, starts from CHAR_BIT - 8 and decrements by 8 in the range of [0, N), if the function is a big endian variant.

Let ptr_bit_index be an integer that starts from 0. Let byte_index8 be index % CHAR_BIT. For each index in the order of the above-specified sequence:

  1. Let byte_mask8 be

    (0x7F << byte_index8), if the function is a signed variant, byte_index8 is equal to (CHAR_BIT - 8), and ptr_bit_index is equal to N - 8;

    — otherwise, (0xFF << byte_index8).

  2. computes value |= (((ptr[ptr_bit_index / CHAR_BIT] & byte_mask8) >> byte_index8) << index);

  3. increments ptr_bit_index by 8.

Finally, if the function is a signed variant, and either:

(ptr[0] >> (CHAR_BIT - 1)) & 0x1 is nonzero for the little endian variant;

— or, (ptr[(N / CHAR_BIT) - 1] >> (CHAR_BIT - 1)) & 0x1 is nonzero for the big endian variant;

then the sign bit is set to 1. Otherwise, it is set to 0.

Returns the computed value.

7.✨.5 Endian-Aware 8-bit Store
Synopsis
#include <stdbit.h>

#if ((N % CHAR_BIT) == 0) && ((CHAR_BIT % 8 == 0)
void stdc_store8_leuN(uint_leastN_t value,
unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_beuN(uint_leastN_t value,
unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_aligned_leuN(uint_leastN_t value,
unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_aligned_beuN(uint_leastN_t value,
unsigned char ptr[static (N / CHAR_BIT)]);

void stdc_store8_lesN(int_leastN_t value,
unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_besN(int_leastN_t value,
unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_aligned_lesN(int_leastN_t value,
unsigned char ptr[static (N / CHAR_BIT)]);
void stdc_store8_aligned_besN(int_leastN_t value,
unsigned char ptr[static (N / CHAR_BIT)]);
#endif
Description

The 8-bit store family of functions functions write a int_leastN_t or uint_leastN_t object into the provided ptr in an endian-aware (7.✨.1) manner, where N matches an existing minimum-width integer type (7.20.1.2). If this function is present, N shall be a multiple of 8 and CHAR_BIT shall be a multiple of 8. The functions containing _aligned in the name shall assume that ptr is suitably aligned to access a signed or unsigned integer of width N. If the function name contains the sN suffix in the name, it is a signed variant. Otherwise, the function is an unsigned variant. If the function name contains the lesN or leuN suffix, it is a little-endian variant. Otherwise, if the function name contains the besN or beuN suffix, it is a big-endian variant.

Let index be an integer in a sequence that

— starts from 0 and increments by 8 in the range of [0, N), if the function is a little endian variant;

— starts from CHAR_BIT - 8 and decrements by 8 in the range of [0, N), if the function is a big endian variant.

Let ptr_bit_index be an integer that starts from 0. Let byte_index8 be index % CHAR_BIT. For each index in the order of the above-specified sequence:

  1. Let byte_mask8 be:

(0x7F << byte_index8), if the function is a signed variant, byte_index8 is equal to (CHAR_BIT - 8), and ptr_bit_index is equal to N - 8;

— otherwise, (0xFF << byte_index8).

  1. Let value_unsigned be:

value if the function is a unsigned variant;

— otherwise, the absolute value of value if the function is a signed variant.

  1. Sets the 8 bits in ptr[ptr_bit_index / CHAR_BIT] at offset byte_index8 to (value_unsigned >> index) & byte_mask8.

  1. Increments ptr_bit_index by 8.

Finally, if the function is a signed variant, and value is less than 0, then either:

ptr[0] has its high bit set to 1 if the function is the little endian variant;

— or, ptr[(N / CHAR_BIT) - 1] has its high bit set to 1 if the function is the big endian variant.

5.4.4. Add a new §7.✨.6 sub-sub-clause for Low-Level Bit Utilities in §7.✨

7.✨.6 Count Leading Zeros
Synopsis
int stdc_leading_zerosuc(unsigned char value);
int stdc_leading_zerosus(unsigned short value);
int stdc_leading_zerosui(unsigned int value);
int stdc_leading_zerosul(unsigned long value);
int stdc_leading_zerosull(unsigned long long value);

generic_return_type stdc_leading_zeros(generic_value_type value);
Returns

Returns the number of consecutive 0 bits in value, starting from the most significant bit.

The type-generic function (marked by its generic_value_type argument) returns the appropriate value based on the type of the input value, so long as it is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

7.✨.7 Count Leading Ones
Synopsis
int stdc_leading_onesuc(unsigned char value);
int stdc_leading_onesus(unsigned short value);
int stdc_leading_onesui(unsigned int value);
int stdc_leading_onesul(unsigned long value);
int stdc_leading_onesull(unsigned long long value);

generic_return_type stdc_leading_ones(generic_value_type value);
Returns

Returns the number of consecutive 1 bits in value, starting from the most significant bit.

The type-generic function (marked by its generic_value_type argument) returns the appropriate value based on the type of the input value, so long as it is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

7.✨.8 Count Trailing Zeros
Synopsis
int stdc_trailing_zerosuc(unsigned char value);
int stdc_trailing_zerosus(unsigned short value);
int stdc_trailing_zerosui(unsigned int value);
int stdc_trailing_zerosul(unsigned long value);
int stdc_trailing_zerosull(unsigned long long value);

generic_return_type stdc_trailing_zeros(generic_value_type value);
Returns

Returns the number of consecutive 0 bits in value, starting from the least significant bit.

The type-generic function (marked by its generic_value_type argument) returns the appropriate value based on the type of the input value, so long as it is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

7.✨.9 Count Trailing Ones
Synopsis
int stdc_trailing_onesuc(unsigned char value);
int stdc_trailing_onesus(unsigned short value);
int stdc_trailing_onesui(unsigned int value);
int stdc_trailing_onesul(unsigned long value);
int stdc_trailing_onesull(unsigned long long value);

generic_return_type stdc_trailing_ones(generic_value_type value);
Returns

Returns the number of consecutive 1 bits in value, starting from the least significant bit.

The type-generic function (marked by its generic_value_type argument) returns the appropriate value based on the type of the input value, so long as it is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

7.✨.10 First Leading Zero
Synopsis
int stdc_first_leading_zerouc(unsigned char value);
int stdc_first_leading_zerous(unsigned short value);
int stdc_first_leading_zeroui(unsigned int value);
int stdc_first_leading_zeroul(unsigned long value);
int stdc_first_leading_zeroull(unsigned long long value);

generic_return_type
  stdc_leading_zero(generic_value_type value);
Returns

Returns the first 0 bit in value, starting from the most significant bit, plus 1. If it is not found, this function returns 0.

The type-generic function (marked by its generic_value_type argument) returns the appropriate value based on the type of the input value, so long as it is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

7.✨.11 First Leading One
Synopsis
int stdc_first_leading_oneuc(unsigned char value);
int stdc_first_leading_oneus(unsigned short value);
int stdc_first_leading_oneui(unsigned int value);
int stdc_first_leading_oneul(unsigned long value);
int stdc_first_leading_oneull(unsigned long long value);

generic_return_type stdc_leading_ones(generic_value_type value);
Returns

Returns the first 1 bit in value, starting from the most significant bit, plus 1. If it is not found, this function returns 0.

The type-generic function (marked by its generic_value_type argument) returns the appropriate value based on the type of the input value, so long as it is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

7.✨.12 First Trailing Zero
Synopsis
int stdc_first_trailing_zerouc(unsigned char value);
int stdc_first_trailing_zerous(unsigned short value);
int stdc_first_trailing_zeroui(unsigned int value);
int stdc_first_trailing_zeroul(unsigned long value);
int stdc_first_trailing_zeroull(unsigned long long value);

generic_return_type
  stdc_first_trailing_zero(generic_value_type value);
Returns

Returns the first 0 bit in value, starting from the least significant bit, plus 1. If it is not found, this function returns 0.

The type-generic function (marked by its generic_value_type argument) returns the appropriate value based on the type of the input value, so long as it is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

7.✨.13 First Trailing One
Synopsis
int stdc_first_trailing_oneuc(unsigned char value);
int stdc_first_trailing_oneus(unsigned short value);
int stdc_first_trailing_oneui(unsigned int value);
int stdc_first_trailing_oneul(unsigned long value);
int stdc_first_trailing_oneull(unsigned long long value);

generic_return_type
  stdc_first_trailing_one(generic_value_type value);
Returns

Returns the first 1 bit in value, starting from the least significant bit, plus 1. If it is not found, this function returns 0.

The type-generic function (marked by its generic_value_type argument) returns the appropriate value based on the type of the input value, so long as it is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

7.✨.14 Rotate Left
Synopsis
unsigned char stdc_rotate_leftuc(unsigned char value, int count);
unsigned short stdc_rotate_leftus(unsigned short value, int count);
unsigned int stdc_rotate_leftui(unsigned int value, int count);
unsigned long stdc_rotate_leftul(unsigned long value, int count);
unsigned long long stdc_rotate_leftull(unsigned long long value, int count);

generic_value_type stdc_rotate_left(
	generic_value_type value, generic_count_type count);
Description
The stdc_rotate_left functions perform a bitwise rotate left. This operation is typically known as a left circular shift.
Returns

Let N be the width corresponding to the type of the input value. Let r be count % N.

— If r is 0, returns value;

— otherwise, if r is positive, returns (value << r) | (value >> (N - r));

— otherwise, if r is negative, returns stdc_rotate_right(value, -r).

The type-generic function (marked by its generic_value_type argument) returns the above described result for a given input value so long as the generic_value_type is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

generic_count_type and generic_return_type type shall be suitably large signed integer types capable of representing the width of the generic_value_type and the computed result, respectively.

7.✨.15 Rotate Right
Synopsis
unsigned char stdc_rotate_rightuc(unsigned char value, int count);
unsigned short stdc_rotate_rightus(unsigned short value, int count);
unsigned int stdc_rotate_rightui(unsigned int value, int count);
unsigned long stdc_rotate_rightul(unsigned long value, int count);
unsigned long long stdc_rotate_rightull(unsigned long long value, int count);

generic_value_type stdc_rotate_right(
	generic_value_type value, generic_count_type count);
Description
The stdc_rotate_right functions perform a bitwise rotate right. This operation is typically known as a right circular shift.
Returns

Let N be the width corresponding to the type of the input value.. Let r be count % N.

— If r is 0, returns value;

— otherwise, if r is positive, returns (value >> r) | (value << (N - r));

— otherwise, if r is negative, returns stdc_rotate_left(value, -r).

The type-generic function (marked by its generic_value_type argument) returns the above described result for a given input value so long as the generic_value_type is

— a standard unsigned integer type, excluding bool;

— an extended unsigned integer type;

— or, a bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

generic_count_type and generic_return_type type shall be suitably large signed integer types capable of representing the width of the generic_value_type and the computed result, respectively.

7.✨.16 Count Ones
Synopsis
int stdc_count_onesuc(unsigned char value);
int stdc_count_onesus(unsigned short value);
int stdc_count_onesui(unsigned int value);
int stdc_count_onesul(unsigned long value);
int stdc_count_onesull(unsigned long long value);

generic_return_type stdc_count_ones(generic_value_type value);
Returns

The stdc_count_ones functions returns the total number of 1 bits within the given value.

The type-generic function (marked by its generic_value_type argument) returns the previously described result for a given input value so long as the generic_value_type is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type shall be a suitably large signed integer type capable of representing the computed result.

7.✨.17 Count Zeros
Synopsis
int stdc_count_zerosuc(unsigned char value);
int stdc_count_zerosus(unsigned short value);
int stdc_count_zerosui(unsigned int value);
int stdc_count_zerosul(unsigned long value);
int stdc_count_zerosull(unsigned long long value);

generic_return_type stdc_count_zeros(generic_value_type value);
Returns

The stdc_count_zeros functions returns the total number of 0 bits within the given value.

The type-generic function (marked by its generic_value_type argument) returns the previously described result for a given input value so long as the generic_value_type is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type for the type-generic function need not be the same as the type of value. It shall be suitably large unsigned integer type capable of representing the computed result.

5.4.5. Add a new §7.✨.3 sub-sub-clause for Fundamental Bit Utilities in §7.✨

7.✨.18 Single-bit Check
Synopsis
bool stdc_has_single_bituc(unsigned char value);
bool stdc_has_single_bitus(unsigned short value);
bool stdc_has_single_bitui(unsigned int value);
bool stdc_has_single_bitul(unsigned long value);
bool stdc_has_single_bitull(unsigned long long value);

bool stdc_has_single_bit(generic_value_type value);
Returns

The stdc_has_single_bit functions returns true if and only if there is a single 1 bit in value.

The type-generic function (marked by its generic_value_type argument) returns the previously described result for a given input value so long as the generic_value_type is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

7.✨.19 Bit Width
Synopsis
int stdc_bit_widthuc(unsigned char value);
int stdc_bit_widthus(unsigned short value);
int stdc_bit_widthui(unsigned int value);
int stdc_bit_widthul(unsigned long value);
int stdc_bit_widthull(unsigned long long value);

generic_return_type stdc_bit_width(generic_value_type value);
Description

The stdc_bit_width functions compute the smallest number of bits needed to store value.

Returns

The stdc_bit_width functions return 0 if value is 0. Otherwise, they return 1 +log2(value).

The type-generic function (marked by its generic_value_type argument) returns the previously described result for a given input value so long as the generic_value_type is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

The generic_return_type type for the type-generic function need not be the same as the type of value. It shall be suitably large signed integer type capable of representing the computed result.

7.✨.20 Bit Floor
Synopsis
unsigned char stdc_bit_flooruc(unsigned char value);
unsigned short stdc_bit_floorus(unsigned short value);
unsigned int stdc_bit_floorui(unsigned int value);
unsigned long stdc_bit_floorul(unsigned long value);
unsigned long long stdc_bit_floorull(unsigned long long value);

generic_value_type stdc_bit_floor(generic_value_type value);
Description
The stdc_bit_floor functions compute the largest integral power of 2 that is not greater than value.
Returns

The stdc_bit_floor functions return 0 if value is 0. Otherwise, they return the largest integral power of 2 that is not greater than value.

The type-generic function (marked by its generic_value_type argument) returns the previously described result for a given input value so long as the generic_value_type is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

7.✨.21 Bit Ceiling
Synopsis
unsigned char stdc_bit_ceiluc(unsigned char value);
unsigned short stdc_bit_ceilus(unsigned short value);
unsigned int stdc_bit_ceilui(unsigned int value);
unsigned long stdc_bit_ceilul(unsigned long value);
unsigned long long stdc_bit_ceilull(unsigned long long value);

generic_value_type stdc_bit_ceil(generic_value_type value);
Description
The stdc_bit_ceil functions compute the smallest integral power of 2 that is not less than value. If the computation does not fit in the given return type, the behavior is undefined.
Returns

The stdc_bit_ceil functions return the smallest integral power of 2 that is not less than value.

The type-generic function (marked by its generic_value_type argument) returns the previously described result for a given input value so long as the generic_value_type is an

— standard unsigned integer type, excluding bool;

— extended unsigned integer type;

— or, bit-precise unsigned integer type whose width matches a standard or extended integer type, excluding bool.

5.5. Add one new entry for Implementation-Defined Behavior in Annex J.3

— The value of __STDC_ENDIAN_NATIVE__ if the execution environment is not big-endian or little-endian (7.✨.1).

— The value of __STDC_ENDIAN_BIG__, and __STDC_ENDIAN_LITTLE__ if the execution environment is not big-endian or little-endian (7.✨.1).

5.6. Modify an existing entry for Unspecified behavior in Annex J.1

— The macro definition of a generic function is suppressed in order to access an actual function (7.17.1) , (7.✨).

6. Appendix

A collection of miscellaneous and helpful bits of information and implementation.

6.1. Decisions to Committee Questions

Originally titled "Committee Polls / Questions", this section listed all of the different pieces of functionality the Committee wanted. Each of the 5 below questions sets of functionality was asked of WG14: nobody raised objections to even want to see a poll on it. This is interpreted as there was unanimous consent amongst participants to include all of this functionality in the paper, even if no formal poll was done for each of the 5 questions. If this changes, it is imperative to let the paper author know.

For the Committee, this proposal is, effectively, five parts:

  1. the endianness definitions;

  2. the stdc_memreverse8 functions (generic and width-specific);

  3. the stdc_load8_*/stdc_store8_* endianness functions;

  4. the suite of low-level bit functions:

    • stdc_count_(leading/trailing)_(ones/zeros),

    • stdc_count_(ones/zeros),

    • stdc_rotate_(left/right), and,

    • stdc_first_(leading/trailing)_(zero/one),

    which map directly to instructions and/or intrinsics; and,

  5. the suite of useful bit functions:

    • stdc_bit_ceil,

    • stdc_bit_floor,

    • stdc_bit_width, and,

    • stdc_has_single_bit,

    which may not map directly to instructions but are useful nonetheless in a wide variety of contexts

These can be polled together or separately, depending on what the Committee desires.

6.2. Example Implementations in Publicly-Available Libraries

Optimized routines following the naming conventions present in this paper can be found in the Shepherd’s Oasis Industrial Development Kit (IDK) library, compilable with a conforming C11 compiler and tested on MSVC, GCC, and Clang on Windows, Mac, and Linux:

Optimized routines following the basic principles present in this paper and used as motivation to improve several C++ Standard Libraries can be found in the Itsy Bitsy Bit Libraries, compilable with a conforming C++17 compiler and tested on MSVC, GCC, and Clang on Windows, Mac, and Linux:

Endianness routines and original motivation that spawned this proposal came from David Seifert’s Portable Endianness library and its deep dive into compiler optimizations and efficient code generation when alignment came into play:

6.3. Implementation of Generic stdc_count_ones

Sample implementation on Godbolt (clang/gcc specific builtins):

#define stdc_count_ones(...) \
	_Generic((__VA_ARGS__), \
		char: __builtin_popcount, \
		unsigned char: __builtin_popcount, \
		unsigned short: __builtin_popcount, \
		unsigned int: __builtin_popcount, \
		unsigned long: __builtin_popcountl, \
		unsigned long long: __builtin_popcountll \
	)(__VA_ARGS__)

int main () {
	return stdc_count_ones((unsigned char)'0') + stdc_count_ones(13ull);
}

6.4. Implementation of Generic stdc_bit_ceil

Sample implementation on Godbolt (clang/gcc specific builtins):

#include <limits.h>

#define stdc_leading_zeros(...) \
	(_Generic((__VA_ARGS__), \
		char: __builtin_clz((__VA_ARGS__)) - ((sizeof(unsigned) - sizeof(char)) * CHAR_BIT), \
		unsigned char: __builtin_clz((__VA_ARGS__)) - ((sizeof(unsigned) - sizeof(unsigned char)) * CHAR_BIT), \
		unsigned short: __builtin_clz((__VA_ARGS__)) - ((sizeof(unsigned) - sizeof(unsigned short)) * CHAR_BIT), \
		unsigned int: __builtin_clz((__VA_ARGS__)), \
		unsigned long: __builtin_clzl((__VA_ARGS__)), \
		unsigned long long: __builtin_clzll((__VA_ARGS__)) \
	))

#define stdc_bit_width(...) \
	_Generic((__VA_ARGS__), \
		char: (CHAR_BIT - stdc_leading_zeros((__VA_ARGS__))), \
		unsigned char: (UCHAR_WIDTH - stdc_leading_zeros((__VA_ARGS__))), \
		unsigned short: (USHRT_WIDTH - stdc_leading_zeros((__VA_ARGS__))), \
		unsigned int: (UINT_WIDTH - stdc_leading_zeros((__VA_ARGS__))), \
		unsigned long: (ULONG_WIDTH - stdc_leading_zeros((__VA_ARGS__))), \
		unsigned long long: (ULLONG_WIDTH - stdc_leading_zeros((__VA_ARGS__))) \
	)

// integer promotion rules means we need to
// precisely calculate the value here
#define __stdc_bit_ceil_promotion_protection(_Type, _Value) \
	_Generic((_Value), \
		char: (_Value <= (_Type)1) ? (_Type)0 : (_Type)(1u <fake-production-placeholder class=production bs-autolink-syntax='<< (stdc_bit_width((_Type)(_Value - 1)) + (UINT_WIDTH - UCHAR_WIDTH)) >>' data-opaque> (stdc_bit_width((_Type)(_Value - 1)) + (UINT_WIDTH - UCHAR_WIDTH)) </fake-production-placeholder> (UINT_WIDTH - UCHAR_WIDTH)), \
		unsigned char: (_Value <= (_Type)1) ? (_Type)0 : (_Type)(1u <fake-production-placeholder class=production bs-autolink-syntax='<< (stdc_bit_width((_Type)(_Value - 1)) + (UINT_WIDTH - UCHAR_WIDTH)) >>' data-opaque> (stdc_bit_width((_Type)(_Value - 1)) + (UINT_WIDTH - UCHAR_WIDTH)) </fake-production-placeholder> (UINT_WIDTH - UCHAR_WIDTH)), \
		unsigned short: (_Value <= (_Type)1) ? (_Type)0 : (_Type)(1u <fake-production-placeholder class=production bs-autolink-syntax='<< (stdc_bit_width((_Type)(_Value - 1)) + (UINT_WIDTH - USHRT_WIDTH)) >>' data-opaque> (stdc_bit_width((_Type)(_Value - 1)) + (UINT_WIDTH - USHRT_WIDTH)) </fake-production-placeholder> (UINT_WIDTH - USHRT_WIDTH)), \
		default: (_Type)0 \
	)

#define stdc_bit_ceil(...) \
	_Generic((__VA_ARGS__), \
		char: __stdc_bit_ceil_promotion_protection(unsigned char, (__VA_ARGS__)), \
		unsigned char: __stdc_bit_ceil_promotion_protection(unsigned char, (__VA_ARGS__)), \
		unsigned short: __stdc_bit_ceil_promotion_protection(unsigned short, (__VA_ARGS__)), \
		unsigned int: (unsigned int)(1u << stdc_bit_width((unsigned int)((__VA_ARGS__) - 1))), \
		unsigned long: (unsigned long)(1ul << stdc_bit_width((unsigned long)((__VA_ARGS__) - 1))), \
		unsigned long long: (unsigned long long)(1ull << stdc_bit_width((unsigned long long)((__VA_ARGS__) - 1))) \
	)

int main () {
	int x = stdc_bit_ceil((unsigned char)'\x13');
	int y = stdc_bit_ceil(33u);
	return x + y;
}

6.5. Endian Enumeration

The endian enumeration was struck from this paper. It had very marginal benefit and was mostly redundant for Standard C code, since the macros would suffice well enough. Nevertheless, the old rationale is presented below.

6.5.1. Rationale

A stdc_endian enumeration could have some benefits, and mirrors the same enumerations come from the (accepted) C++20 paper and idioms found in [p0463], which also went into a <bit> header. Similar ideas are also present in libraries such as [libcork-byte-order], which are hybrid C and C++ libraries that give definitions similar to the ones here. Compilers also define macros such as __BYTE_ORDER__ (Clang/GCC family), or are well-defined to be a certain endianness (Windows is always little-endian).

The other portion of this is that providing an enumeration helps users pass this information along to functions. Users defining functions that take an endianness, without the enumeration, would define it as so:

void my_conversion_unsafe(int endian, size_t data_size,
	unsigned char data[static data_size]);

The name may specify that it is for an endian, but the range of values is not really known without looking at the documentation. It is also impossible for the compiler to diagnose problematic uses: calling my_conversion(4595944, 4, ptr); is legal, and compilers will not diagnose such a call as wrong. Now, consider the same with the enumeration:

void my_conversion_safe(stdc_endian endian, size_t data_size,
	unsigned char data[static data_size]);

This function call can get diagnosed in (some) implementations:

#include <stddef.h>

typedef enum stdc_endian {
	stdc_endian_little = __ORDER_LITTLE_ENDIAN__,
	stdc_endian_big = __ORDER_BIG_ENDIAN__,
	stdc_endian_native = __BYTE_ORDER__,
} stdc_endian;

void my_conversion_unsafe(int endian, size_t n, unsigned char ptr[static n]) {}
void my_conversion_safe(stdc_endian endian, size_t n, unsigned char ptr[static n]) {}

int main () {
	unsigned char arr[4];
	my_conversion_unsafe(48558395, sizeof(arr), arr);
	my_conversion_safe(48558395, sizeof(arr), arr);
	//                 ^
	// <source>:15:24: error: integer constant not in range 
	// of enumerated type 'stdc_endian' (aka 'enum stdc_endian') [-Werror,-Wassign-enum]
	my_conversion_unsafe((stdc_endian)48558395, sizeof(arr), arr);
	my_conversion_safe((stdc_endian)48558395, sizeof(arr), arr);
	return 0;
}

(Many current implementations do not diagnose it in the current landscape because such implicit conversions are, unfortunately, incredibly common, sometimes for good reason.)

7. Acknowledgements

Many thanks to David Seifert, Aaron Bachmann, Jens Gustedt, Tony Finch, Erin AO Shepherd, and many others who helped fight to get the semantics and wording into the right form, providing motivation, giving example code, pointing out existing libraries, and helping to justify this proposal.

References

Informative References

[ANDERSON-BIT-HACKS]
Sean Eron Anderson. Bit Twiddling Hacks. May 5th, 2005. URL: https://graphics.stanford.edu/~seander/bithacks.html
[ARM-SETEND]
armKEIL. SETEND instruction: ARM and Thumb instructions. December 31st, 2019. URL: https://www.keil.com/support/man/docs/armasm/armasm_dom1361289895072.htm
[CLANG-BUILTINS]
LLVM Foundation; Clang Contributors. Clang Language Extensions: Clang Documentation. September 1st, 2021. URL: https://clang.llvm.org/docs/LanguageExtensions.html#intrinsics-support-within-constant-expressions
[ENDIAN-FALLACY]
Rob Pike. The Byte Order Fallacy. April 3rd, 2012. URL: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
[GCC-BUILTINS]
GCC Contributors. Other Built-in Functions Provided by GCC. September 1st, 2021. URL: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
[LIBCORK-BYTE-ORDER]
Douglas Creager. libcork: Byte order. November 22nd, 2017. URL: https://libcork.io/0.15.0/byte-order.html
[LINUX-ENDIAN]
Linux; BSD. endian(3). September 1st, 2021. URL: https://linux.die.net/man/3/endian
[MSVC-BUILTINS]
Microsoft. _byteswap_uint64, _byteswap_ulong, _byteswap_ushort. November 4th, 2016. URL: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/byteswap-uint64-byteswap-ulong-byteswap-ushort?view=msvc-160
[N2731]
ISO/IEC JTC1 SC22 WG14 - Programming Languages, C; JeanHeyd Meneide; Freek Wiedijk. N2731: ISO/IEC 9899:202x - Programming Languages, C. October 18th, 2021. URL: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2731.pdf
[NTOHL]
Linux. ntohl(3). September 30th, 2021. URL: https://linux.die.net/man/3/ntohl
[P0463]
Howard E. Hinnant. endian. Just endian.. July 13th, 2017. URL: https://wg21.link/p0463
[P0553]
Jens Maurer. Bit operations. March 1st, 2019. URL: https://wg21.link/p0553
[PORTABLE-ENDIANNESS]
David Seifert. portable-endianness. May 16th, 2021. URL: https://github.com/SoapGentoo/portable-endianness
[RUST-COUNT_ONES]
Rust Standard Library Collaborators. u32 methods: count_ones. November 10th, 2021. URL: https://doc.rust-lang.org/std/primitive.u32.html#method.count_ones
[SDCC]
Dr. Philipp K. Krause. SDCC Manual §8.1.9 - Bit Rotations. September 25th, 2021. URL: http://sdcc.sourceforge.net/doc/sdccman.pdf
[TI-TMS320C64X]
Texas Instruments. TMS320C64x/C64x+ DSP: CPU and Instruction Set. July 31st, 2010. URL: https://www.ti.com/lit/ug/spru732j/spru732j.pdf