Date: Thu, 4 Sep 2025 14:22:55 +0000
> > You can't use _BitInt(384) to represent that specialized register because it needs an alignment of 64Bytes.
> Why would a specialised 384 bit register need 64 byte alignment? What justification do you have for that?
Because of the cache line.
This is how a modern CPU works.
-----Original Message-----
From: David Brown <david.brown_at_hesbynett.no>
Sent: Thursday, September 4, 2025 14:27
To: Tiago Freire <tmiguelf_at_[hidden]>; std-proposals_at_lists.isocpp.org
Subject: Re: [std-proposals] D3666R0 Bit-precise integers
On 03/09/2025 18:24, Tiago Freire wrote:
>> No one has mentioned aliasing! You can't suddenly jump in with a different topic entirely and pretend that's what you were talking about all along.
>
> This isn't a gotcha conversation. This really should be very easy to understand.
>
> This is what you said of _BitInt
>
>> When you are dealing with hardware with 9-bit integers, or an SHA accelerator with 384-bit registers, you want types that have the exact size.
>
Yes. Ideally, you want the types to be contained in objects of the exact size too, but that depends on hardware availability. 9-bit registers might be fine in an FPGA but are unlikely to be available on a mainstream cpu. However, the type can still be 9 bits of value, plus some padding bits to fit to an appropriate container size.
> And this:
>
>> But it seems natural (to me) that alignment of a _BitInt should
>> normally be [...] uint64_t for larger _BitInt's. (That's what C23
>> does.)
Yes.
>
> Let's focus on sha registers.
> What is the alignment of this variable?
> _BitInt(384) shaBuffer; //< This one
384 bits, which is 48 bytes (octets), and suitably contained in the equivalent of an array of 6 uint64_t's with uint64_t alignment (which will depend on the platform).
>
> If statement number 2 is true, the answer is 8Bytes, then the statement 1 is wrong.
No, I don't get it. It can be 8-byte aligned and an exact size of 384 bits (logically 384 bits, which is important for software, and happily also 384 bits of storage size in this case).
> You can't use _BitInt(384) to represent that specialized register because it needs an alignment of 64Bytes.
Why would a specialised 384 bit register need 64 byte alignment? What justification do you have for that?
And why would any of this /need/ a 384 bit register ? An FPGA (or other specialist hardware) implementation might well use a 384-bit register, but it might not - the type could be stored in other general registers, other memory, SIMD registers, a mixture of these, or any other possibilities open to implementations working with data of any type.
And if the data happens to be stored in a 384-bit register, alignment is not relevant.
> If the answer is 64Bytes then not only statement 2 is false, it is also incompatible with C.
> Does it have an alignment of 8Bytes or 64Bytes? It can't be both!
>
Since your 64-byte alignment seems to be pulled out of the air, I can't answer that very well. 8-byte alignment seems fine to me in this case.
> And this thing where you associate a "_BitInt(384)" to a "sha384 register" is called aliasing!
>
No, it is not.
>
>> No one is suggesting that _BitInt types should alias other types, or that people will assume they alias other types.
>
> When you say "_BitInt(384)" can be used for "SHA accelerator with 384-bit registers", that's exactly what it means.
>
No, it does not.
>
>
>
> The rest of the explanation of values/objects are just wrong. A description of the language is not a description of the machine.
> Here's a short rundown:
>
>> Here there is one /object/ - of type "int", and with the identifier "i". The object here is created at compile time ("new" or "malloc" are used to create long-lasting objects at run time, while local variables are short-lived objects)
> It's not. Can be on the stack, can be on a register, it can be simply rolled-out and converted to an immediate, it can be completely optimized away have no runtime equivalent.
>
>> Objects are boxes, and have addresses
> They don't have to.
> Objects in C++ have a different meaning, I'm giving the benefit of the doubt that you are using the term loosely here.
> Again, they don't have too, see above.
>
>
>> /Values/ do not have addresses or any other method of storage (registers or whatever).
>
> That’s an abstract language description. This is not how machines work.
The C++ standards /are/ an abstract language description!
If you want to see a specification of a particular machine, read the
reference manuals for that hardware. If you want to talk about C++,
talk about C++ - it is language for an abstract machine. It is right
there in the C++ standards - section 4.1.1 "Abstract machine", under
"General principles". (The numbering is from C++20 - section numbers
may vary between standards versions, but text is the same.) And if you
are not taking about the standard C++ language defined by the C++
standards, why are we having this conversation on a C++ standards
proposals mailing list?
> All values must be somewhere on the machine. They are a physical thing that exists on a computer. They must be represented somewhere in hardware, or they literally don't exist.
>
Learn to distinguish hardware and software.
>> The results of the evaluation of an expression are values but not objects - they may then be stored in an object, but may also be discarded or used in other expressions.
>
> Isn't that just a contradiction of the previous statement? Let's no pretend we don't actually know where these things are, we know exactly where these things are.
> It's either a flag, a register, an immediate, or sent to an addressable storage (I'm not going to count other types of hardware).
Learn to distinguish hardware and software.
"int x = 1 + 2;" is a perfectly good line of software code in C++. Two
values (not objects), 1 and 2, are added to get the resulting value (not
object) of 3. "x" is declared as an object of type "int" (let's assume
it's a local variable). The object "x" is initialised with the
calculated value, 3.
That's the language viewpoint.
If none of the rest of the code uses the value of "x", then the
implementation - the object code to run on the hardware, does none of
this at all. Neither the object nor the values exist anywhere in the
hardware.
Hardware does not have "objects" and "values" in the sense that the
programming language does (in the language, objects are distinct blocks
of memory, and values all have types). It has storage spaces - memory,
registers, etc. These contain data in binary representation that can be
manipulated in different ways by instructions. A register does not
contain the int value 3 - it contains a bit pattern 00000011. Whether
that represents an int value 3, a pointer, an enumeration, or anything
else, depends entirely on how it is used.
> Assembly instructions are issued to move the values between the different types of storage. There's no such thing as a value without a physical representation.
>
Yes, there is.
>> A local variable that is not initialised does not have a value
>
> This is just wrong. A local variable that is uninitialized has a value, the value it contains is undefined, and what the compiler decides to do with it is undefined.
No, it has no value in the normal sense. Until C++26, it is said to
have an "indeterminate value", from C++26 onwards it is said to have an
"erroneous value". These may have a bit representation that matches a
valid value of the type - they may not. For example, an uninitialised
local variable of pointer type may have a bit representation that is not
suitably aligned, and an uninitialised bool variable may have a bit
representation that is not either 0 or 1. In some cases, the variable
may have a trap representation (this is uncommon on most hardware, but
is the norm for variables allocated to registers on the Itanium).
If the code attempts to read the local variable while it is
indeterminate or erroneous, the behaviour is undefined or erroneous.
> But it most definitely has a value, it can't be otherwise because they represent physical storage that only has 2 states and does not have an unset 3rd state.
Nope.
First, hardware /can/ have support for more detail than this as an aide
to debugging or catching errors. The Itanium's general-purpose
registers have 64-bits for data, and 1 bit for "valid/empty". (They may
have more than that - I don't know all the details.)
Secondly, "value" is a programming language term, not a hardware
implementation term. Languages and compilers often know more about the
data than just the bit representation.
Thirdly, some data types are stored in containers with more possible bit
representations than valid values of the type. Examples include
pointers with alignment requirements greater than 1, and bool.
> The machine is physically unable to do otherwise.
> You don't have to take my word for it.
> You can try this code in every compiler (as long as you get it to stop complaining).
>
> int i;
> std::cout << i << std::endl;
>
> You will see that a value is always printed.
>
If you grab someone off the street and torture them until they tell you
where the treasure is buried, they'll give you an answer. That does not
make it useful information. Bullying a compiler into giving you object
code for rubbish source code does not tell you anything useful - it's
all undefined behaviour.
>
> -----Original Message-----
> From: David Brown <david.brown_at_hesbynett.no>
> Sent: Wednesday, September 3, 2025 16:32
> To: Tiago Freire <tmiguelf_at_hotmail.com>; std-proposals_at_lists.isocpp.org
> Subject: Re: [std-proposals] D3666R0 Bit-precise integers
>
>
>
> On 03/09/2025 15:36, Tiago Freire wrote:
>>>> What says who? What guarantees your compiler when you specify _BitInt(9) to go "nah he wants a specialized 9bit FPGA register" instead of just giving you a 16bit register with 7 unused bits?
>>> That will be up to the compiler.
>>
>> Precisely! It's going to choose whatever, not what you whished it would have been.
>> It's Voodoo programming.
>>
>
> No, that kind of thing will be implementation-defined behaviour. It is up to the compiler, but will be fixed and should be documented. It's the same for countless other things in C and C++ that you rely on every day, such as the size of "int" or your favourite, alignment requirements.
>
>>
>>>>> How can something that is not yet a feature of the language, have "always" been UB?
>>>> It already exists in C. That the argument, whatever C is doing C++ will adopt for interoperability, there is no room for alternative behavior here.
>>> In C, alignment is not UB.
>>
>> Alignment is implementation defined. Aliasing is the UB part!
>>
>
> No one has mentioned aliasing! You can't suddenly jump in with a
> different topic entirely and pretend that's what you were talking about
> all along.
>
> C++ has quite clear and strict definitions about what lvalues are
> allowed to alias each other. _BitInt will not be different. No one is
> suggesting that _BitInt types should alias other types, or that people
> will assume they alias other types.
>
>>
>>> Alignment is about memory addresses. [...] /Values/ don't have alignments.
>>
>> That's wrong, that's just wrong.
>> The compiler can optimize "values" to not have an address, because it maybe a temporary that is short lived enough to fit and complete it's journey in a register, and constants can be embedded in the instructions themselves.
>> But your CPU only has so many registers, at which point it has to swap them to memory, values in memory must have an address and that address must be aligned.
>> To claim that "values don't have alignments", and therefore it doesn't matter is just wrong. That's not how it works.
>
> I'm sorry, but you are completely mixing up programming languages with
> hardware implementations, and have a fundamental misunderstanding of the
> terminology.
>
> The distinction between objects and values - the little boxes that can
> hold data, and the data that is in them - is critical to the way coding
> works. /Values/ do not have addresses or any other method of storage
> (registers or whatever). But they are kept in /objects/, which do.
>
> static int i;
> for (i = 0; i < 1'000'000; i++) { }
>
> Here there is one /object/ - of type "int", and with the identifier "i".
> The object here is created at compile time ("new" or "malloc" are used
> to create long-lasting objects at run time, while local variables are
> short-lived objects). Begin "static", it is allocated a fixed place in
> the program's memory image - thus it has an address that does not change
> (and it is appropriately aligned).
>
> At run time, it contains a series of /values/ - from 0 up to a million.
> A million different values exist during the loop - but only one object.
> The values will be stored in the object, and read from the object, but
> they are not objects themselves.
>
> It is correct that compiler optimisation can often elide the need for a
> logical object to be allocated a memory slot in the implementation - but
> that is an implementation detail that is irrelevant to how the language
> works and what any particular bit of C++ code means. I've used C on
> microcontrollers that have no registers at all - I've also used it on
> microcontrollers that have no data memory (only registers). Nothing
> about the hardware implementation has any relevance for how the language
> is defined.
>
>
> Look at the C++ standards, at the very first entry in "Terms and
> definitions" :
>
> """
> access (execution-time action)
> read or modify the value of an object
> """
>
> Not all values are held in objects. The results of the evaluation of an
> expression are values but not objects - they may then be stored in an
> object, but may also be discarded or used in other expressions.
>
> Not all objects contain values. A local variable that is not
> initialised does not have a value - attempting to read from it is UB
> (or, I think "erroneous behaviour" in C++26, though I don't quite follow
> the distinction).
>
> Not all use of memory is for objects. There can be padding between
> objects in memory, to ensure alignments are correct. Space in a stack
> can be used by the compiler for temporary storage of partial results -
> values - during expression evaluation.
>
>
> Objects are boxes, and have addresses and often have names (unless they
> are dynamically allocated from the heap). Values are the things you
> keep in the boxes, and have no addresses or names. After writing "int
> x;", "x" is an object, "42" is a value that you can store in "x" by
> writing "x = 42". You cannot store the object "x" in the value "42" by
> writing "42 = x".
>
>
>>
>> Not to mention that the registers that will be used for general arithmetic are likely different from the ones used for sha operations.
>>
>> I can just summarize this entire conversation as: That's not how it works.
>>
>
> I hope that my explanation above can correct your misunderstandings. If
> not, I think it will be best to drop this thread. Let those that
> understand the point of _BitInt use them, and let those that don't see
> them as useful ignore them. C++ is vast - no one has need of it all,
> and I doubt if anyone understands it all.
>
>
>>
>>
>> -----Original Message-----
>> From: David Brown <david.brown_at_hesbynett.no>
>> Sent: Wednesday, September 3, 2025 10:39
>> To: Tiago Freire <tmiguelf_at_hotmail.com>; std-proposals_at_lists.isocpp.org
>> Subject: Re: [std-proposals] D3666R0 Bit-precise integers
>>
>>
>>
>> On 03/09/2025 07:58, Tiago Freire wrote:
>>>
>>>> Well, you can only get the exact sizes on platforms that support those exact sizes. On other platforms, the _BitInt types need to be contained in something larger if they are cannot be provided directly. So a _BitInt(9) is likely to need an FPGA or special hardware for its size to be exactly 9 bits. But they can be implemented in an obvious way on other platforms, just with padding bits. Alignment will be implementation dependent, just as it is for all other types.
>>>
>>> What says who? What guarantees your compiler when you specify _BitInt(9) to go "nah he wants a specialized 9bit FPGA register" instead of just giving you a 16bit register with 7 unused bits?
>>>
>>
>> That will be up to the compiler.
>>
>> "Compilers" for FPGA hardware are not like compilers for normal conventional serial cpus. The trend for at least some parts of FPGA design is to use languages like C (and to a currently much lesser extent, a subset of C++) to describe the operation. But the compiler does not allocate variables to memory addresses with alignments, or use cpu registers, or generate a sequence of cpu instructions for doing arithmetic. It generates hardware registers, hardware adder or arithmetic blocks, hardware step machines and sequencers. You write your code in a particular specialised way, so that it can be turned into hardware.
>>
>> And then you take that same code, compile it with a desktop C++ compiler, and get the same results from the calculations. (Actually, you usually do the desktop simulations first.) You are not concerned about alignment - you are concerned about having the same values.
>>
>>>
>>>> How can something that is not yet a feature of the language, have "always" been UB?
>>>
>>> It already exists in C. That the argument, whatever C is doing C++ will adopt for interoperability, there is no room for alternative behavior here.
>>>
>>
>> In C, alignment is not UB.
>>
>>>
>>>> Alignment is not an issue when converting anything. Converting between types is done by value. Alignment only matters for pointers.
>>>> And the alignment of different types is not UB, it is implementation-defined behaviour.
>>>
>>> What? It applies to operations on "values".
>>
>> No, it does not.
>>
>> Alignment is about memory addresses. If a particular type (say, a
>> uint32_t) on a given implementation has a 4-byte alignment, then all
>> valid addresses to uint32_t have their lowest two bits clear. If you
>> construct an address where the lowest two bits are not 0 (say, by
>> converting from a char pointer, or an uintptr_t) and try to use that as
>> a pointer to access a uint32_t, then you have UB. It /might/ work -
>> many hardware platforms support unaligned accesses. It /might/ cause a
>> hardware exception or other crash - some instructions on some cpus do
>> not support aligned access (such as some SIMD instructions). Since it
>> is UB, the compiler can assume it never happens, which can affect code
>> generation - it's UB, so you have no guarantees of any kind.
>>
>> /Values/ don't have alignments.
>>
>>> Why do think there exists such a thing as an alignment requirement?
>>> If it's not a register or is part of the instruction. EVERYTHING is in memory and has an address.
>>
>> No, everything is /not/ in memory, nor does everything have an address.
>>
>> /Values/ don't have addresses. /rvalues/ do not have addresses -
>> objects and lvalues have addresses. The number 42 does not have an
>> address. If x and y are ints, then (x + y) is has a value and type int,
>> but does not have an address. If you write "int foo = 23;", then the
>> /object/ "foo" has an address, and it contains the /value/ 23. The
>> value in foo has no address. The object "foo" will be put in memory
>> (baring optimisation and the "as if" rule) at an address that satisfies
>> the implementation-defined (not UB!) alignment requirements for an "int"
>> in that particular implementation. The value 23 that is contained in
>> "foo" has no alignment requirements, because it is a value.
>>
>>> You can't emit an instruction to operate on your "values" (which are in memory and therefore referenced in assembly by an address) because that is out of spec for the hardware. And because you can't control exactly what instructions your compiler is going to emit, it's not "implementation-defined", it's undefined. Nobody knows what's going to happen.
>>> This is not how computers work.
>>
>> I know how computers work. I know how programming languages work. I
>> suspect you are misunderstanding what objects are (in C and C++
>> terminology) and what values are. Hopefully my explanation above has
>> made things a bit clearer, so we can move on.
>>
>>>
>>> That's why when you say things like "When you are dealing with hardware with 9-bit integers, or an SHA accelerator with 384-bit registers, you want types that have the exact size."
>>> And I ask "How does _BitInt(384) operate on SHA accelerator with 384-bit registers when it's not aligned?", and now you are telling me "It doesn't! We just copy it over to a type that does."...what are we talking about?
>>>
>>> Do you see the problem?
>>
>> I see that you are misunderstanding me, and that is a problem for this
>> conversation.
>>
>> Forget about alignment - it is a minor implementation detail.
>>
>> Forget, for now, about the actual sizes of the containers for the
>> _BitInt types. In C and C++ it is perfectly allowable for types to have
>> containers that are bigger than the value bits, so that you have extra
>> padding bits. (A prime example is "bool", which is typically
>> implemented in an 8-bit byte with one value bit and 7 padding bits.)
>>
>> Then understand what the _BitInt types do - what they model. Understand
>> how these can be useful in software when you want to model an integer
>> type with a specific number of bits. C and C++ are high-level languages
>> - they are defined in terms of an abstract machine, not specific
>> hardware, and their types are for use in software. At the high level,
>> alignment is irrelevant and the size of containers is usually equally
>> irrelevant (or easily handled - such as by using "sizeof" in your
>> memcpy() calls).
>>
>> Then understand that C and C++ are languages intended to be efficient on
>> real hardware, and for low-level non-portable systems programming.
>> Appreciate how fixed-size integer types can map to hardware. Sometimes
>> they can be used in ways that map to common hardware, sometimes to very
>> specialised hardware. In some cases, precise size of containers now
>> becomes relevant because your accesses are not simply free accesses
>> within a read-write memory block. And at the lowest level of the
>> hardware, if you are talking about objects in memory rather than just
>> values, alignment must match up.
>>
>>
>>>
>>> -----Original Message-----
>>> From: David Brown <david.brown_at_[hidden]esbynett.no>
>>> Sent: Tuesday, September 2, 2025 23:17
>>> To: Tiago Freire <tmiguelf_at_[hidden]>; std-proposals_at_lists.isocpp.org
>>> Subject: Re: [std-proposals] D3666R0 Bit-precise integers
>>>
>>>
>>>
>>> On 02/09/2025 18:38, Tiago Freire wrote:
>>>> See here's my problem:
>>>>
>>>>> When you are dealing with hardware with 9-bit integers, or an SHA accelerator with 384-bit registers, you want types that have the exact size.
>>>>
>>>>> Ultimately, this will be implementation-dependent. But it seems
>>>>> natural (to me) that alignment of a _BitInt should normally be that
>>>>> of the any standard unsigned integer type that can contain them, or
>>>>> uint64_t for larger _BitInt's. (That's what C23 does.)
>>>>
>>>> These 2 statements are contradictory. It is either one or the other and it can't be both.
>>>
>>> Well, you can only get the exact sizes on platforms that support those exact sizes. On other platforms, the _BitInt types need to be contained in something larger if they are cannot be provided directly. So a
>>> _BitInt(9) is likely to need an FPGA or special hardware for its size to be exactly 9 bits. But they can be implemented in an obvious way on other platforms, just with padding bits. Alignment will be implementation dependent, just as it is for all other types.
>>>
>>>> The real answer is actually neither, this is actually UB. It's all UB, it always has been.
>>>
>>> How can something that is not yet a feature of the language, have "always" been UB?
>>>
>>>> Since alignment is not specified converting it to anything is UB.
>>>
>>> Alignment is not an issue when converting anything. Converting between types is done by value. Alignment only matters for pointers.
>>>
>>> And the alignment of different types is not UB, it is implementation-defined behaviour.
>>>
>>>> Maybe it works on some hardware that are tolerant with alignment, and some implementations may try to align with what you probably want to do anyways, but it's still UB.
>>>>
>>>
>>> _BitInt types would always be accessed via pointers that are appropriately aligned, unless you are playing silly buggers with converting pointer types - just like for all other types.
>>>
>>>> You want to work with specialized registers? The best way is for the compiler to provide concrete types that satisfy both the size and alignment requirements for those registers.
>>>
>>> If you have hardware that works best with types of a particular bit size, why not have standardised types that are of that particular bit size - to the best of the target implementation's abilities?
>>>
>>>> You can't just use a _BitInt and expect it to align because it doesn't have too, and according to you own words it can't be used for larger specialized registers, that's what you and everyone wants to do but that is not what it actually does.
>>>>
>>>
>>> Why would you not have to have appropriate alignment for _BitInt types?
>>>
>>>> That's why I picked that example. It's either a chunky boy or it doesn't align, the end.
>>>>
>>>
>>> For C23 _BitInt, _BitInt(512) will have the same alignment as uint64_t.
>>> I would expect the same for C++ _BitInt.
>>>
>>>
Received on 2025-09-04 14:23:00