Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is size guaranteed to be a multiple of alignment? #306

Open
alercah opened this issue Nov 12, 2021 · 11 comments
Open

Is size guaranteed to be a multiple of alignment? #306

alercah opened this issue Nov 12, 2021 · 11 comments

Comments

@alercah
Copy link

alercah commented Nov 12, 2021

It's currently documented in the reference that size must be a multiple of alignment. But it's not clear if this is an invariant guaranteed to hold forever, or whether it's something that might one day not hold true if we permit over-aligned types. The main obstacle to those, I believe, would be defining whether arrays are padded or not and how one might construct the other kind.

There has been some discussion about this in rust-lang/rfcs#1397 and elsewhere, but a use case has actually arisen now: C++ interop. C++ allows derived classes to make use of the base class's padding bytes, and also has an attribute [[no_unique_address]] that causes a struct field's tail padding to be reused. This relies on general assignment in C++ through pointers not being able to clobber padding, which is different from Rust. The discussion of the property applies to some other use cases as well e.g. of over-aligned stack variables (which are necessary in some cases such as crypto code). Currently the only way to get an aligned stack variable is to put it in an over-aligned type, which means wasting stack space entirely unnecessarily on padding.

Note that the linked issue initially proposes enabling this by default; I do not think that should be permitted as I think the definition of padding that the UCG have arrived at is generally a good one. But alternative proposals, such as an opt-in attribute, would all rely on some facility to separate the stride and size of a type. Layout already shows that stride doesn't need an explicit representation, and additionally provides an API for code to easily calculate the layout of arrays in a way that is agnostic to whether or not this invariant holds.

Given the Rust definition of padding, it seems more appropriate to treat such cases as if they do not actually have padding, and actually lower the size understood by Rust to omit the trailing padding.

While I'm here, I'd like to confirm that the omission of tail padding (or head padding, even) in the glossary is a mistake?

@alercah
Copy link
Author

alercah commented Nov 12, 2021

There's also the possibility of alignment 0 which is used in Ada to mean a value may not even be aligned to a storage unit (bitfields). It's maybe also worth considering whether bitfield support in Rust may wish to support this someday.

@Diggsey
Copy link

Diggsey commented Nov 12, 2021

I think this is an issue with terminology. Size is always a multiple of alignment, even in C++. From the docs for sizeof:

When applied to a class type, the result is the number of bytes occupied by a complete object of that class, including any additional padding required to place such object in an array. The number of bytes occupied by a potentially-overlapping subobject may be less than the size of that object.

It sounds like what you are referring to is the "size - tail padding"? I could imagine introducing some special behaviour for tail padding if it makes C++ interop easier, but I don't think that changes the size of the type for all other uses of the word "size".

@alercah
Copy link
Author

alercah commented Nov 12, 2021

No, I'm suggesting the possibility that tail padding could be omitted entirely from the object, bringing down its size. This is what Swift does, for instance: https://developer.apple.com/documentation/swift/memorylayout.

@Diggsey
Copy link

Diggsey commented Nov 12, 2021

Swift is just changing the terminology. They're using the word "stride" instead of "size", and "size" to mean "size without padding".

Code that cares about sizeof in C++ or sizeof in Rust would use stride in Swift. There is no meaningful difference beyond switching the words.

@RalfJung
Copy link
Member

Cc #176

While I'm here, I'd like to confirm that the omission of tail padding (or head padding, even) in the glossary is a mistake?

We did not intend to exclude head/tail padding, so yeah this should probably be clarified.

@RalfJung
Copy link
Member

It looks like the reference considers this issue already settled. https://doc.rust-lang.org/reference/type-layout.html says

The size of a value is always a multiple of its alignment.

@alercah
Copy link
Author

alercah commented Jul 20, 2022

Indeed, but I wouldn't take that as a statement about what must be true in the future. But there is definitely code relying on the assumption, so changing this would be a backwards compatibility nightmare. I'm inclined to say we consider the matter settled until someone wants to seriously make a proposal for how to address that.

@Lokathor
Copy link
Contributor

I think entirely new targets might (big might!) be able to change it.

for all current targets, it's essentially settled forever.

@scottmcm
Copy link
Member

Even on new targets, it's forced by the existence of https://doc.rust-lang.org/core/array/fn.from_ref.html so long as sizeof([T; N]) ≡ sizeof(T) * N, which I think is even harder to change.

@alercah
Copy link
Author

alercah commented Jul 20, 2022

I think that's actually less of an issue; it just constrains arrays of undersized types to also be undersized at the final element. And frankly I see no issue with that.

It's worth noting that alloc::Layout does not make the same assumption (in the API; it does in the code, but that's fine). So there is at least some leeway to change things there.

But I'm not sure how I follow that it's target-specific. Unless you're suggesting using the speculative feature of targets that provide fewer valid assumptions as a way to force unsafe packages to opt-in to supporting these kinds of types. Which could be viable, I suppose, but it seems less like a target-specific thing to me as the folks working on C++ interop stories would love to see this on existing targets.

@Lokathor
Copy link
Contributor

Yes I'm saying that for existing targets you definitely can't break this promise (even if it was an accidental promise).

but for a new target you can potentially do a lot of crazy things and then say "also if your code can't cope with this just don't use that crate on this target until it's updated (if ever)."

should new targets do crazy things? probably not, but if a crazy thing has to be done a new target is about the only way to do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants