RFC: attribute((no_sizeof))

Hi all,

I’d like to propose (or learn about if something similar already exists) the following type attribute for clang:

__attribute__((sizeless)).

As the name implies, attaching this to a type tells clang to forbid uses of sizeof on the type, as well as any type containing it (e.g. there is no sizeof for an array of said type).

The original context for this idea arose in cross-platform vector extensions where an SVE is sizeless already and forbids sizeof, but vector types for other platforms may still have a size. This makes writing ``correct’’ cross-platform code hard, since what is a legal operation may dependent on the platform, and regardless this should be forbidden always.

This also may be interesting to use elsewhere, say by applying more aggressively forbidding sizeof in certain user code to discourage manually allocating the size of memory in bytes (potentially reducing memory errors).

1 Like

IMO “sizeless” sounds like the structure should take up no memory. Why not __attribute((forbid_sizeof))?

1 Like

Sure and I agree, sizeless was just the name from the clang API: clang: clang::Type Class Reference

I wonder if we could do this in a slightly more complex manner that would feel more natural. We already have #pragma pack that effects structures that are defined between them. We also have #pragma poison which poisons certain function calls. Could we extend the poison pragma to support #pragma clang poison sizeof and associate that with the struct? That feels like it fits more naturally.

1 Like

In terms of naming, I think I prefer sizeless to no_sizeof – I expect we’ll start asking questions like “what should alignof return for a sizeless type?” or “are sizeless types actually trivially copyable or not?” etc. and so tying the attribute to no_sizeof won’t let us extend the behavior very easily.

I also wonder if there will be enough use of this attribute in the wild to justify it? Sizeless types are not a common thing and I’d expect use of this attribute to be with a subset of the people using sizeless types. I don’t have a good idea of how many folks that would possibly be.

Maybe a bit of a naive question, but can sizeof() be made to “do the right thing” and query the dynamic vector length on types where that makes sense?

Yeah I kind of imagine this to be more of a feature used by developers than users (e.g. someone building a vectorization library, which is what prompted the RFC). However, this will then impact users, because it will be a way to forbid users from going out of spec and using sizeof on the type (which depending on platform they built against, is already forbidden).

So my impression here is that sizeof needs to be a compile time constant per the spec. Changing it to exist for this case could be possible, but that sounds like a much larger language semantics change.

Thanks, this would indeed be helpful for Highway :slight_smile: We’ve had users write incorrect-but-working-on-x86 code, and it would be nice to prevent this.

I’ve drafted a commit which successfully implements the functionality here: GitHub - wsmoses/llvm-project at sizeless

Am curious for folks thoughts (and if no objections, I’ll open a PR shortly).

Here’s the PR ([Clang] add user-level sizeless attribute by wsmoses · Pull Request #71894 · llvm/llvm-project · GitHub). I went with the name sizeless per @AaronBallman’s suggestion and also forbade alignof as well.

What is the semantics of these things?
Can they be used as parameters? In coroutines? be returned? be constant evaluated? put on the stack? How does they affect new?
When is the layout set? code gen, link time, runtime? I suspect all of these are valid use cases.

I guess it is not clear to me how just disabling sizeof is sufficient to enable sizeless types.

Apologies for my late reply, this all took place while I was away at WG21.

Note that there IS a few uses of sizeof that are not compile-time constants, VLAs in particular. Sizeless types share quite a bit in common with VLAs… Not that I’m claiming this is a good idea, but it perhaps deserves some discussion.

That said, why is the attribute necessary? Shouldn’t this apply to any ‘sizeless type’ regardless? We do the 'cant use sizeof on types containing flexible array members, perhaps we should do it here?.

My impression of @jan-wassenberg’s use case (and please correct me here) is something like this

// library code
#ifdef SVE_SUPPORTED
typedef svfloat_t floatvec;
#else
typedef float floatvec __attribute__((ext_vector_type(4)));
#endif

...

// user code
data.forall([](floatvec& out, floatvec in) {
    out += in;
    // sizeof(in) may be used by use code here if sve is not supported.
    // however its use creates portability problems, so enforcing sizeof
    // to not be legal on floatvec independent of platform is desirable
});

I guess it is not clear to me how just disabling sizeof is sufficient to enable sizeless types.

By itself it is not, but disabling sizeof would be another guardrail against introducing cross-platform incompatibilities by users that only test on certain platforms.

My impression of @jan-wassenberg’s use case (and please correct me here) is something like this

Right, that’s quite close. If it makes a difference, instead of ext_vector_type we’d use a class wrapper if !SVE_SUPPORTED and would apply the attribute to that.

Will it be possible to use the attribute in C also? I had wanted something like this recently, to make it possible to hide implementation details from a public header, while helping users to avoid making mistakes either trying to allocate the object with sizeof or copy it. In C++, I theorize I might have been able to at least make those mistakes much more inconvenient already by making the constructors private and disallowing forms such as copy assignment. I had briefly hoped that adding a C99 flexible array member to the end would make sizeof an error, since there seem to be no entirely correct uses for sizeof on such a struct–but alas that sizeof result seemed to be defined in the standard.

Yup! and the linked PR I have has a test with c code specifically

1 Like

This proposal really worries me because the behavior is very particular to the emulation of how SVE types currently behave.

It sounds like it might be a more generic feature, which as vtjnash suggests, could be used to prevent the size of your struct from being part of the ABI. But it doesn’t do that at all: it won’t forbid allocating the object on the stack (encoding the fixed size), or copying the object by-value as function parameters or return values or = assignment (all encoding the fixed size), etc.

I also find it a bit weird that SVE types have this behavior in the first place. If you can pass these type by value (which you can), both size and alignment clearly must be available to the compiler. Yes, their size is dynamically computed based on vscale, instead of constant, but they do have a size. (and I don’t understand why alignment is prohibited; that one actually isn’t vscale-dependent?)

1 Like