C++ Logo

std-proposals

Advanced search

Re: [std-proposals] [[packed]] std::unaligned

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Sun, 10 Dec 2023 16:10:43 +0000
On Sun, 2023-12-10 at 09:59 -0500, Arthur O'Dwyer via Std-Proposals
wrote:
> On Sat, Dec 9, 2023 at 7:20 PM Lénárd Szolnoki via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
> > On Sat, 2023-12-09 at 22:14 +0000, Frederick Virchanza Gotham via
> > Std-Proposals wrote:
> > > On Saturday, December 9, 2023, Frederick Virchanza Gotham wrote:
> > > >
> > > > In both of these use cases, I'd much prefer if we could do the
> > > > following:
> > > >
> > > > struct TokenInfo {
> > > > long unsigned a;
> > > > char b;
> > > > long unsigned c;
> > > > char d;
> > > > };
> > > >
> > > > typedef struct TokenInfo [[packed]] TicketInfo;
> > > >
> > > > So now 'TicketInfo' is a packed form of the 'TokenInfo' struct.
> >
> > An attribute is the wrong tool for this as it is ignorable. Maybe
> > once
> > metaclasses land then it could be feasible, and still a pure
> > library
> > implementation.
> >
>
>
> Actually, we were just arguing about this (basically because
> different people have vastly different ideas of what "ignorable" was
> ever supposed to mean in the first place) over in this CWG reflector
> thread: https://lists.isocpp.org/core/2023/11/15103.php It turns out
> that struct layout is pretty much the most implementation-defined
> thing in the world, and thus "ignorable" by (some) definition —
> because an implementation can just say "This attribute does nothing
> on my platform's ABI" and that's okay. (MSVC does this with
> [[no_unique_address]]. That's extremely low QoI, but there's nothing
> wrong with it from the paper standard's point of view.)
> So [[packed]] — that is, standardizing the existing practice of
> __attribute__((packed)) — would be totally fine.

[[no_unique_address]] affects layout compatibility, which is observable
through is_layout_compatible. [[packed]] would arguably affect layout
compatibility similarly.

[[packed]] is troublesome in other ways too, as you shouldn't be able
to bind a reference or get the address of a member to a packed class.
(AFAIK there is implementation divergence here between gcc and clang
too.) So depending on whether [[packed]] is ignored certain expressions
may become ill-formed, similarly how taking the address of a bit-field
is ill-formed.

Compilers ignore [[packed]] right now, compilers switching to interpret
it as __attribute__((packed)) would be an ABI break without a good
reason. Yes, [[no_unique_address]] was an ABI break too. Granted, the
break only affects new libraries that support old compilers, and they
can avoid the break by just not using the attribute. gcc and clang
adopted the attribute early enough to not cause additional ABI issues,
but this issue is still there.

> However, I don't think Frederick has realized yet that his "packed
> TokenInfo" (where the attribute cracks open the curly braces and
> applies itself to all subobjects recursively) is-not-a kind of
> "TokenInfo". There are things you can do with a "TokenInfo" which you
> physically cannot do with such a "packed TokenInfo." One example is
> "fetch its `c` member with an aligned load instruction" (because the
> packed TokenInfo's `c` is misaligned). Another example is "evaluate
> `&p->c` and have it give you a pointer of type `long unsigned*`"
> (because the language assumes that it's always safe to load from a
> `long unsigned*` using an aligned load instruction).
> Clang and GCC just make this a
> warning: https://godbolt.org/z/vGGWYMq64
> IIRC, Green Hills provides `__packed` as a type qualifier (like
> `const` and `volatile`), where taking the address of a `__packed int
> pi` just gives you a `__packed int *ppi`, and the compiler
> understands that loading from `*ppi` will require a misaligned load.
>
> I don't fully see what people are envisioning even with this
> `std::unaligned<T>` syntax. I mean, you could make it work like
> `std::atomic<T>`, and maybe that's good enough, and what people are
> expecting —
> std::unaligned<int> ai = 0;
> int x = ai.load(); // copy out the value
> but you certainly couldn't make it work like `T` itself —
> std::unaligned<int> ai = 0;
> int *p = &ai.value(); // point to the held value? no! physically
> impossible!
> std::unaligned<MyTrivial> at;
> at->my_member_function(); // call a member function on the held
> value? no! physically impossible!
> (Did/does Green Hills allow you to write a member function `void
> my_member_function() __packed { ~~~ }` receiving a __packed-qualified
> this pointer? I don't recall. Probably not. But it would be
> consistent.)

The point is that you put std::unaligned around members of a packed
struct, not around the struct itself. It's not used the same way as
__attribute__((packed)). The thing is __attribute__((packed)) is a
convenience attribute for a struct that mainly affects its members.
std::unaligned would make it explicit without introducing weird bit-
field-like language rules for these members.

Later if metaclasses will happen, then convenience std::packed
metaclass could wrap each member into std::unaligned.

I'm not saying that this idea is fully fledged out though. You can take
the address of a std::unaligned member and you get a
std::unaligned<T>*. A common type between std::unaligned<T>* and T*
might be warranted, as there is no harm in a "pointer to a possibly
unaligned int" to point to either an unaligned int or just a regular
int object.

Maybe the feature would be better as a language feature after all, but
I still think that it should primarily be a property of a member and
not the property of the parent class itself. One (probably wild) idea
is to make `alignas(N)` a qualifier, and allow qualification conversion
in the safe direction. And lift the ban on specifying lower than the
types alignment for members.


Cheers,
Lénárd

Received on 2023-12-10 16:10:48