Date: Fri, 16 Jan 2026 18:29:14 +0100
> The implementation cannot not copy the padding bits when the user
> invokes memcpy.
That is wrong. There is no way to observe that padding bits have been
copied anyway, so this can be optimized away under the as-if rule. In fact,
memcpy can (and in practice is) optimized to a mov between registers when
possible, and a mov of x87 floating-point types does not copy padding bits
because none exist in the first place.
It's like arguing that creating an int variable requires the compiler to
put four bytes on stack memory. Just no.
> It could theoretically do something funny with them when
> the user invokes bit_cast, but that is still acceptable if the result is
> usable (specifically, those bits of the result that were not produced
> from the input padding bits).
>
Yes, in fact it does something funny with constant evaluation; as
mentioned, LLVM does not have an object representation and does not have
any tracked padding bits at compile time. Object representations for
bit-cating are generated on the fly.
Noone is proposing to rely on padding bits. On the contrary, I and David
> propose to add an utility for clearing padding bits, which, if anything,
> could improve security.
>
But your utility has no observable effect in the abstract machine. Padding
bits do not really exist (see the two examples I just brought up). You're
clearing bits that don't really exist, and the user has no way to observe
their values anyway.
I'm not opposed to a function whose effect is completely
implementation-defined (similar to std::breakpoint) if you want to
standardize __builtin_clear_padding, but that's as far as we can go with
the object model.
> > There are still floating-point types where certain representations
> > result in CPU traps when used in an operation, if you find that to be
> > more convincing of a reason.
>
> Yes. And the possibility of producing a trap value of such a type exists
> whether or not it is produced via padding or value representation bits
> in the source.
>
Producing representations not valid for the type is a separate issue, and
should stay UB.
> > memcpy has well defined behavior, regardless of how it's
> implemented. If
> > the program says memcpy then the source is copied as is to the
> target,
> > with padding bits and everything. I do not see a fundamental reason
> why
> > then the target should not be allowed to be used.
> >
> > Because the source could also not be used. For all intents and purposes,
> > padding bits are eternally indeterminate quantum bits, and querying
> > their value results in your CPU exploding.
>
> This premise is too disconnected from reality.
>
> Look, even the standard acknowledges that objects are represented with
> bytes stored in some kind of storage.
No, this is wrong. See the aforementioned examples.
> memcpy must write something to the target bits where the source padding
> bits map onto. You could argue that memcpy could magically produce
> garbage there instead of actually reading it from source (although no
> implementation does such silly things, to my knowledge), but that would
> still be acceptable if the user doesn't use those garbage bits. And I
> maintain, the user should be allowed to do that, I see no reason why he
> shouldn't.
>
No, it's not required to do anything like that because you're not able to
observe whether it actually copied memory. Memory is an implementation
detail, and the implementation is free to not copy anything if it knows
that all the input bytes are indeterminate anyway. There is no observable
distinction between any two indeterminate bytes.
So what you're saying is that there is no legal way to bit_cast a
> _BitInt(3) to something else (e.g. uint8_t), that this is a good thing?
> I'm sorry, but I disagree.
>
I'm saying it's not a good thing, and it would be useful to have something
like std::bit_cast_zero_padding, or for padding wipes to be the default
behavior of std::bit_cast. I just don't see any need to overhaul the object
model, possibly disallowing vast amounts of optimizations.
I'm also saying that the approach of wiping the padding bits in the object
before it gets bit-cast doesn't make any sense, considering those bits
don't really exist, or if they exist, are very magical, even if you think
of them as being much more predictable and well-behaved than the object
model actually says.
> But padding bits exist and are exposable in various ways. Denying this
> is living in a delusional world and results in people writing
> non-portable and non-conforming code anyway.
>
That's not a nice way of putting it. It's also questionable why considering
padding-bits to be unpredictable would result in non-portable and
non-conforming code. It seems like a cautious and conservative model.
If anything, it's more dangerous to rely on memcpy to copy padding bits
just because that's your intuition and mental model, without any wording to
actually support this, and with various known implementations contradicting
that mental model.
> invokes memcpy.
That is wrong. There is no way to observe that padding bits have been
copied anyway, so this can be optimized away under the as-if rule. In fact,
memcpy can (and in practice is) optimized to a mov between registers when
possible, and a mov of x87 floating-point types does not copy padding bits
because none exist in the first place.
It's like arguing that creating an int variable requires the compiler to
put four bytes on stack memory. Just no.
> It could theoretically do something funny with them when
> the user invokes bit_cast, but that is still acceptable if the result is
> usable (specifically, those bits of the result that were not produced
> from the input padding bits).
>
Yes, in fact it does something funny with constant evaluation; as
mentioned, LLVM does not have an object representation and does not have
any tracked padding bits at compile time. Object representations for
bit-cating are generated on the fly.
Noone is proposing to rely on padding bits. On the contrary, I and David
> propose to add an utility for clearing padding bits, which, if anything,
> could improve security.
>
But your utility has no observable effect in the abstract machine. Padding
bits do not really exist (see the two examples I just brought up). You're
clearing bits that don't really exist, and the user has no way to observe
their values anyway.
I'm not opposed to a function whose effect is completely
implementation-defined (similar to std::breakpoint) if you want to
standardize __builtin_clear_padding, but that's as far as we can go with
the object model.
> > There are still floating-point types where certain representations
> > result in CPU traps when used in an operation, if you find that to be
> > more convincing of a reason.
>
> Yes. And the possibility of producing a trap value of such a type exists
> whether or not it is produced via padding or value representation bits
> in the source.
>
Producing representations not valid for the type is a separate issue, and
should stay UB.
> > memcpy has well defined behavior, regardless of how it's
> implemented. If
> > the program says memcpy then the source is copied as is to the
> target,
> > with padding bits and everything. I do not see a fundamental reason
> why
> > then the target should not be allowed to be used.
> >
> > Because the source could also not be used. For all intents and purposes,
> > padding bits are eternally indeterminate quantum bits, and querying
> > their value results in your CPU exploding.
>
> This premise is too disconnected from reality.
>
> Look, even the standard acknowledges that objects are represented with
> bytes stored in some kind of storage.
No, this is wrong. See the aforementioned examples.
> memcpy must write something to the target bits where the source padding
> bits map onto. You could argue that memcpy could magically produce
> garbage there instead of actually reading it from source (although no
> implementation does such silly things, to my knowledge), but that would
> still be acceptable if the user doesn't use those garbage bits. And I
> maintain, the user should be allowed to do that, I see no reason why he
> shouldn't.
>
No, it's not required to do anything like that because you're not able to
observe whether it actually copied memory. Memory is an implementation
detail, and the implementation is free to not copy anything if it knows
that all the input bytes are indeterminate anyway. There is no observable
distinction between any two indeterminate bytes.
So what you're saying is that there is no legal way to bit_cast a
> _BitInt(3) to something else (e.g. uint8_t), that this is a good thing?
> I'm sorry, but I disagree.
>
I'm saying it's not a good thing, and it would be useful to have something
like std::bit_cast_zero_padding, or for padding wipes to be the default
behavior of std::bit_cast. I just don't see any need to overhaul the object
model, possibly disallowing vast amounts of optimizations.
I'm also saying that the approach of wiping the padding bits in the object
before it gets bit-cast doesn't make any sense, considering those bits
don't really exist, or if they exist, are very magical, even if you think
of them as being much more predictable and well-behaved than the object
model actually says.
> But padding bits exist and are exposable in various ways. Denying this
> is living in a delusional world and results in people writing
> non-portable and non-conforming code anyway.
>
That's not a nice way of putting it. It's also questionable why considering
padding-bits to be unpredictable would result in non-portable and
non-conforming code. It seems like a cautious and conservative model.
If anything, it's more dangerous to rely on memcpy to copy padding bits
just because that's your intuition and mental model, without any wording to
actually support this, and with various known implementations contradicting
that mental model.
Received on 2026-01-16 17:29:29
