-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Tracking issue for alloc_layout_extra #55724
Comments
After reading over these again, we may want to either include |
In the case of |
Why isn't the same argument true for |
Because in a |
See #55747 for an issue caused by misuse of the current API. Would be great if we could do something to avoid such mistakes. |
The fundamental question here is: do we want to allow layouts where the size is not a multiple of the alignment? And if we do then we will want to add a convenience method to round up a |
Also if we do we should at least add a fat warning in the And maybe the default allocator should |
Fix Rc/Arc allocation layout * Rounds allocation layout up to a multiple of alignment * Adds a convenience method `Layout::pad_to_align` to perform rounding Closes rust-lang#55747 cc rust-lang#55724
FWIW, we actually have explicit tests that allocate with size 8, alignment 16 -- i.e., with a size that is not a multiple of the alignment: |
I stumbled upon That is, per https://doc.rust-lang.org/nightly/src/core/alloc.rs.html#95, Which means:
IOW, So it seems to me we can drop the |
What exactly needs to be done to stabilize this? What questions has to be answered before? |
Hello, just wondering if there's any stable alternative to |
The alternative is |
I think there might be a bug in the implementation of |
I'd like to propose stabilization for at least The longer these go without being stabilized, the more ad-hoc re-implementations we're going to see. I know myself that I wrote incorrect layout calculation for What is stabilizedThe functions Combined, these functions allow code to construct and manipulate /// Returns the layout of a repr(C) struct with the given field layouts in the given order,
/// and the individual field's offsets.
pub fn repr_c(fields: &[Layout]) -> (Layout, Vec<usize>) {
let mut offsets = Vec::new();
let mut layout = Layout::from_size_align(0, 0).unwrap();
for field in fields {
let (new_layout, offset) = layout.extend(*field).unwrap();
offsets.push(offset);
layout = new_layout;
}
// Don't forget trailing padding!
(layout.pad_to_align().unwrap(), offsets)
} |
I am still worried about having to do this:
I saw experienced Rust compiler devs forget to call this. This is a huge footgun. The heavy-weight approach to this would be a dedicated type for layouts where this padding has already been done -- letting the type-system track if
|
Automatically padding to align or requiring padding to align is probably not desirable. Consider something like Swift's decoupled size/stride; it's not incorrect to have a layout without trailing padding to align, it's just that this is the common default for both But I definitely agree that we should fix the |
Ever decoupling this for real in Rust will be near impossible due to lots of unsafe code already assuming size == stride. But yes.
Just make it |
Sorry, I had a typo there. pub fn repr_c<const N: usize>(fields: [Layout; N]) -> Result<(Layout, [usize; N]), LayoutErr> {
unsafe {
let mut offsets: MaybeUninit<[usize; N]> = MaybeUninit::uninit();
let mut layout = Layout::from_size_align_unchecked(0, 0);
let mut offset = offsets.as_mut_ptr().cast::<usize>();
for field in &fields[..] {
let (new_layout, this_offset) = layout.extend(*field)?;
layout = new_layout;
ptr::write(offset, this_offset);
offset = offset.offset(1);
}
Ok((layout.pad_to_align()?, offsets.assume_init()))
}
} (NB: this is unsound as currently implemented as |
Oh I see. Yeah that's kind-of annoying... |
Its been ages since I looked at this stuff, but: Can't you use 1 instead of 0 as the initial align? |
This is what I'm using currently, as it avoids the need for pub fn repr_c<const N: usize>(fields: [Layout; N]) -> Result<(Layout, [usize; N]), LayoutErr> {
let mut offsets: [usize; N] = unsafe { mem::zeroed() }; // replacement for `[0; N]` which doesn't work yet
let mut layout = fields[0];
for i in 1..N {
let (new_layout, this_offset) = layout.extend(fields[i])?;
layout = new_layout;
offsets[i] = this_offset;
}
Ok((layout.pad_to_align()?, offsets))
} EDIT 2019-04-15 const generics and alloc_layout_extra have improved a bit from when I first wrote pub fn repr_c<const N: usize>(fields: [Layout; N]) -> Result<(Layout, [usize; N]), LayoutErr> {
let mut offsets: [usize; N] = [0; N];
let mut layout = fields[0];
for i in 1..N {
let (new_layout, this_offset) = layout.extend(fields[i])?;
layout = new_layout;
offsets[i] = this_offset;
}
Ok((layout.pad_to_align(), offsets))
} |
Layout::pad_to_align is infallible As per [this comment](#55724 (comment)) (cc @glandium). > Per https://github.com/rust-lang/rust/blob/eb981a1/src/libcore/alloc.rs#L63-L65, `layout.size()` is always <= `usize::MAX - (layout.align() - 1)`. > > Which means: > > * The maximum value `layout.size()` can have is already aligned for `layout.align()` (`layout.align()` being a power of two, `usize::MAX - (layout.align() - 1)` is a multiple of `layout.align()`) > * Incidentally, any value smaller than that maximum value will align at most to that maximum value. > > IOW, `pad_to_align` can not return `Err(LayoutErr)`, except for the layout not respecting its invariants, but we shouldn't care about that. This PR makes `pad_to_align` return `Layout` directly, representing the fact that it cannot fail.
Stabilize most common subset of alloc_layout_extras Tracking issue: rust-lang#55724 Specifically, this stabilizes: ```rust pub fn Layout::align_to(&self, align: usize) -> Result<Layout, LayoutErr>; pub fn Layout::pad_to_align(&self) -> Layout; pub fn Layout::extend(&self, next: Layout) -> Result<(Layout, usize), LayoutErr>; pub fn Layout::array<T>(n: usize) -> Result<Layout, LayoutErr>; ``` Methods that are tracked by rust-lang#55724 but are not stabilized here: ```rust pub fn Layout::padding_needed_for(&self, align: usize) -> usize; pub fn Layout::repeat(&self, n: usize) -> Result<(Layout, usize), LayoutErr>; pub fn Layout::repeat_packed(&self, n: usize) -> Result<Layout, LayoutErr>; pub fn Layout::extend_packed(&self, next: Layout) -> Result<Layout, LayoutErr>; ``` Combined, these stabilized functions allow code to construct and manipulate `repr(C)` layouts while letting the standard library handle correctness in the face of edge cases. For example use cases, consider the usage in [hashbrown](https://github.com/Amanieu/hashbrown/blob/2f2af1d/src/raw/mod.rs#L143), [crossbeam-skiplist](https://github.com/crossbeam-rs/crossbeam-skiplist/blob/master/src/base.rs#L99), [pointer-utils/slice-dst](https://github.com/CAD97/pointer-utils/blob/92aeefeed9399f28d1b1654b63f8dcbe1242d8d4/crates/slice-dst/src/layout_polyfill.rs), and of course the standard library itself. Providing a higher-level API such as `Layout::repr_c<const N: usize>(fields: [Layout; N]) -> Result<(Layout, [usize; N]), LayoutErr>` is blocked on const generics, which are a ways off. Providing an API that doesn't provide offsets would be quite suboptimal, as the reason for calculating the layout like this rather than `Layout::new` is to get the field offsets. The primary issue with the current API is having to call `.pad_to_align()` to match the layout of a `repr(C)` struct. However, I think this is not just a (failing? limitation?) of the API, but rather intrinsic complexity. While all Rust-defined types have size==stride, and probably will for the foreseeable future, there is no inherent reason why this is a limitation of all allocations. As such, the `Layout` manipulation APIs shouldn't impose this limitation, and instead the higher level api of `repr_c` (or just plain old using `Layout::new`) can make keeping it simple. cc @matklad r? @rust-lang/libs
I'll speak a moment about This can't be done using However, manually calling |
Could |
For
|
Curious, are there any major blockers for stabilizing |
Really, all of these are pretty good and useful, not just dangling. I'd be in favor of stabilizing them. |
There is no "dangling" in the summary issue at the top; is that one incomplete? |
I know // modulo dealing with offsets
let mut this = self;
for _ in 1..n {
this = this.extend(self)?;
}
this and not have trailing padding, or if it should include trailing padding like I'll go ahead and prepare a stabilization PR with the cleanup I think is desirable and we can FCP that. (EDIT: #97438) Footnotes
|
Thanks for all of the detail and putting that stabilization PR together, @CAD97! |
Well, I put one in #55724 (comment) above
I think you're looking at the wrong thing -- there's an I'd be happy to make a PR to expose that type so it could be used in EDIT: I opened an ACP to add an |
Does |
I think at this point cc @CAD97 who last tried to stabilize some of these methods. |
I have updated the API summary at the top of this issue with the current
|
This issue tracks additional methods on
Layout
which allow layouts to be composed to build complex layouts.The main use case is to construct complex allocation layouts for use with the stable global allocator API. For example:
std::collections::HashMap
hashbrown
crossbeam-skiplist
One concern is that not many of these methods have been extensively used in practice. In the examples given above, only
extend
,array
andalign_to
are used, and I expect that these will be the most used in practice.padding_needed_for
is used in the implementation ofRc::from_raw
andArc::from_raw
, but in theory could be superseded by the offset returned byextend
.The text was updated successfully, but these errors were encountered: