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

[type-layout] Document minimum size and alignment #1482

Closed
wants to merge 3 commits into from

Conversation

joshlf
Copy link
Contributor

@joshlf joshlf commented Mar 11, 2024

This is a draft implementation of rust-lang/rust#117474 (comment). I'm happy to make any suggested edits.

cc @RalfJung

- For a [trait object](#trait-object-layout), the minimum size is 0 and the minimum
alignment is 1
- For a struct type with a dynamically-sized field, the minimum size is taken to be
the size of the struct when the dynamically-sized field has *its* minimum size.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alignment affects size, so discussing the two separately here doesn't quite work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting just adjusting it to say this?

For a struct type with a dynamically-sized field, the minimum size is taken to be the size of the struct when the dynamically-sized field has its minimum size and alignment.

(Done. I can revert if I misunderstood your suggestion.)

this property provides *no* guarantees about [`repr(Rust)`](#the-rust-representation)
types, as such a type can have arbitrarily large size and alignment regardless of
the sizes and alignments of its fields
- Every type's minimum size is less than or equal to `isize::MAX`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bullet doesn't belong into the list of "notable cases of minimum size and alignment are", it belongs before or after the list. Also it should say what happens when one declares a type whose minimum size exceeds this limit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bullet doesn't belong into the list of "notable cases of minimum size and alignment are", it belongs before or after the list.

Done.

Also it should say what happens when one declares a type whose minimum size exceeds this limit.

Do we need to specify that? Presumably the implication is just that such a program would not conform to the spec, and so we couldn't permit it to compile, but a) that's a logical implication of the text here and, b) I don't believe we need to specify the manner in which it fails to compile.

I'm happy to also specify that such programs won't compile - I just want to make sure I'm understanding correctly that this is just a nice-to-have rather than something that affects the semantics of this text.

Copy link
Member

@RalfJung RalfJung May 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to specify that?

I think so, yes. If we leave it implicit it could understood to be anything from UB to a compile-time error. (We don't do "UB by omission" in Rust but people might think we do. And some sort of run-time panic does seem like a not unreasonable interpretation to me.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable. Presumably resolving #1482 (comment) is required in order for us to agree on a wording for this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The questions of "which types are checked" and "what happens when a type fails the check" seem orthogonal to me, so I don't see an overlap with the other discussion.

@RalfJung
Copy link
Member

RalfJung commented Apr 4, 2024

There's a potential issue here -- not all types mentioned in the source code actually make it to codegen, so sometimes types that are "too big" do not lead to errors.

This example fails to build in a debug build but succeeds as a release build:

#![crate_type = "lib"]

pub fn f() {
    g::<u16>();
}
pub fn g<T>() -> std::mem::MaybeUninit<[T; 1 << 47]> {
    std::mem::MaybeUninit::uninit()
}

@ehuss ehuss added the S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author. label Apr 25, 2024
@joshlf
Copy link
Contributor Author

joshlf commented May 11, 2024

There's a potential issue here -- not all types mentioned in the source code actually make it to codegen, so sometimes types that are "too big" do not lead to errors.

This example fails to build in a debug build but succeeds as a release build:

#![crate_type = "lib"]

pub fn f() {
    g::<u16>();
}
pub fn g<T>() -> std::mem::MaybeUninit<[T; 1 << 47]> {
    std::mem::MaybeUninit::uninit()
}

Good call. This shouldn't be a problem in our motivating use case, but obviously we need to be careful with what we promise.

Intuitively, the guarantee is something like "a type which you can do anything with" or "a type which you can create a pointer to". But I'm not sure how to formalize that idea.

For the motivating use case, we need to guarantee that this holds of *const T if I can actually execute code that synthesizes a *const T.

@RalfJung
Copy link
Member

The new wording sounds good to me.

However, the issue with "which types actually end up being size-checked" remains. I don't have a good idea for how to express that.

@rust-lang/opsem @rust-lang/lang: any thoughts? There's tow questions; the easy one is what you think about the text of the PR, but the harder one is this concern. Specifically, the question is for which types we guarantee that their size is less than isize::MAX. You may think the answer is "all of them", but that's not true -- if a type gets optimized away before codegen, it may never be size-checked.

I don't have a good idea for how to even say which types are guaranteed to be size-checked in the current implementation. Similar to what we did for const-eval failures, we could try to have a notion of "required types" such that if a function runs, these types are definitely size-checked -- but given the huge number of types in a typical program, that sounds like it could be quite expensive.

@joshlf
Copy link
Contributor Author

joshlf commented May 29, 2024

Is there some notion of "observability" we could use here? As in, if a type's existence is "observable" in some way? Intuitively, I'd imagine this just means that if the execution of a program in any way depends on the existence of the type (calling size_of::<T>(), constructing a *const T, etc), although I'm not sure if that notion is formalized anywhere.

@RalfJung
Copy link
Member

RalfJung commented May 29, 2024

The only things that are "observable" about a program are its IO behavior (and volatile accesses). That's the usual as-if rule. So I don't think that will help us here.

- For a [trait object](#trait-object-layout), the minimum size is 0 and the minimum
alignment is 1
- For a struct type with a dynamically-sized field, the minimum size is taken to be
the size of the struct when the dynamically-sized field has *its* minimum size and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not clear on what "it" refers to in this sentence

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the answer is value-dependent, e.g., for Foo<dyn Blah> the alignment depends on the value stored in the vtable -- put another way, it's determined by the "erased information"

Copy link
Member

@RalfJung RalfJung Jul 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean "its"? It is straight-forward recursion. If a struct has field types T, U, V where V is dyn-sized, then the min size of the struct is computed using the min size and min align of V.

@RalfJung
Copy link
Member

RalfJung commented Jul 3, 2024

FWIW after further discussion, my conclusion is that all we really need for @joshlf's usecase right now is rust-lang/rust#126152. That avoids having to document when exactly compilation fails due to types being bigger than isize::MAX.

IOW, the entire point of this PR is to be able to say "Every type's minimum size is less than or equal to isize::MAX", but that statement has problems.

@traviscross
Copy link
Contributor

@rustbot labels -I-lang-nominated

OK. Per RalfJ's comment then, let's close in favor of rust-lang/rust#126152.

If further discussion manages to work out some solution to the problems that RalfJ mentioned, feel free to reopen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants