Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upAllocator integration #42313
Conversation
rust-highfive
assigned
aturon
May 30, 2017
This comment has been minimized.
This comment has been minimized.
|
r? @aturon (rust_highfive has picked a reviewer for you, use r? to override) |
pnkfelix
force-pushed the
pnkfelix:allocator-integration
branch
2 times, most recently
from
971fd63
to
9f69470
May 31, 2017
This comment has been minimized.
This comment has been minimized.
|
@pnkfelix while this is waiting for review, looks like there are some failures with debuginfo tests which need updating e.g.
|
aidanhs
added
the
S-waiting-on-author
label
Jun 1, 2017
gereeter
suggested changes
Jun 1, 2017
|
I'm really happy to see this, and it looks like the integration works quite smoothly, which is great. I certainly don't see any fundamental issues. I'm a teensy bit disappointed (but not surprised) to not see my |
| ptr: Address, | ||
| layout: Layout, | ||
| new_layout: Layout) -> Result<(), CannotReallocInPlace> { | ||
| let (_, _, _) = (ptr, layout, new_layout); |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 1, 2017
Contributor
Can this check usable_size and allow the reallocation if the new layout fits within the usable size of the old layout? See also my comment here, which seemed fairly uncontroversial.
| let s = new_layout.size(); | ||
| // All Layout alignments are powers of two, so a comparison | ||
| // suffices here (rather than resorting to a `%` operation). | ||
| if min <= s && s <= max && new_layout.align() <= layout.align() { |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 1, 2017
Contributor
Can this instead just call realloc_in_place and rely on that to do the usable_size check? See also my comment here, which seemed fairly uncontroversial.
| let size = size as isize; | ||
| let p = self.alloc(layout); | ||
| if let Ok(p) = p { | ||
| for i in 0..size { |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 1, 2017
Contributor
Can this instead use core::ptr::write_bytes? That should be more efficient.
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 1, 2017
Author
Member
Ah yes I briefly skimmed through the core::ptr source but didn't look at the reexports, so I missed this. Will fix.
| /// not a strict requirement. (Specifically: it is *legal* to use | ||
| /// this trait to wrap an underlying native allocation library | ||
| /// that aborts on memory exhaustion.) | ||
| unsafe fn alloc(&mut self, layout: Layout) -> Result<Address, AllocErr>; |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 1, 2017
Contributor
I didn't see this mentioned in the RFC discussion, but it occurs to me... why should this be unsafe? The main reason I can see is that allocators can assume that the layouts they are passed are non-zero, but that ought to be specified in the documentation for alloc.
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 1, 2017
Contributor
Also, that seems more like something to consider for alloc_unchecked. Should alloc return Unsupported for zero-sized allocations?
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 1, 2017
Author
Member
Hmm. I personally don't think it is a burden to make fn alloc an unsafe method, since most clients will either need to immediately use unsafe code to initialize the allocated state, or will be passing along an uninitialized block of memory which means that they themselves will probably be unsafe fn's as well...
But I agree that its important to spell out all of the criteria for what safe input arguments are, e.g. requiring layouts have non-zero size...
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 1, 2017
•
Author
Member
Actually, now that I've gone and read the man page for malloc and jemalloc more carefully, I no longer think that passing a size of zero is undefined behavior...? It says its implementation defined whether you get back a null or a valid non-null address (that you're nonetheless not allowed to dereference), but I don't think it allows for arbitrary UB, does it?
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 1, 2017
Author
Member
(still, even if its not UB, it doesn't actually solve our own problem, since the alloc::heap implementation of fn check_size_and_alignment includes a debug assert that size != 0, so we would need to either side step that or include an appropriate check up here...)
| /// Creates a layout describing the record for `n` instances of | ||
| /// `self`, with a suitable amount of padding between each. | ||
| /// | ||
| /// Requires non-zero `n` and no arithmetic overflow from inputs. |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 1, 2017
Contributor
The requirement for non-zero n is not checked anywhere, even in the (checked) repeat function. It doesn't really seem necessary (and is counterintuitive for me) that 0 is not allowed.
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 1, 2017
•
Author
Member
We have to make a choice about where we are going to attempt to enforce that people do not make zero-sized memory requests to the allocator.
Given how many iterations the API for Layout and Allocator went through, at different times I had different models in my head about whose responsibility it was to ensure that the underlying system allocator did not receive requests for zero-sized memory...
anyway, I'll try to resolve this. not 100% sure which way I'll go (I don't think I have a clear "right direction" from prior discussions on the RFC thread...)
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 1, 2017
Author
Member
(At this point I'm leaning towards "Layout can have zero size; it is responsibility of client to ensure either that Layout it passes to an allocator has positive size, or that it is using an allocator that can handle zero-sized requests.)
| let old_size = layout.size(); | ||
| let result = self.alloc(new_layout); | ||
| if let Ok(new_ptr) = result { | ||
| ptr::copy(ptr as *const u8, new_ptr, cmp::min(old_size, new_size)); |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 1, 2017
Contributor
I think this can be copy_nonoverlapping. It would be an invalid allocator implementation to have two outstanding allocations that overlap.
| /// are set to zero before being returned. | ||
| unsafe fn alloc_zeroed(&mut self, layout: Layout) -> Result<Address, AllocErr> { | ||
| let size = layout.size(); | ||
| if size > ::core::isize::MAX as usize { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 1, 2017
Author
Member
because one can currently construct a Layout that overflows isize?
I suppose I could instead just say that the state of the contents are unspecified if the layout size overflows isize. Does that seem preferable?
This comment has been minimized.
This comment has been minimized.
| let new_align = cmp::max(self.align, next.align); | ||
| let realigned = Layout { align: new_align, ..*self }; | ||
| let pad = realigned.padding_needed_for(new_align); | ||
| let offset = self.size() + pad; |
This comment has been minimized.
This comment has been minimized.
| pub fn padding_needed_for(&self, align: Alignment) -> usize { | ||
| debug_assert!(align <= self.align()); | ||
| let len = self.size(); | ||
| let len_rounded_up = (len + align - 1) & !(align - 1); |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 1, 2017
Contributor
Arguably, this should actually be wrapping arithmetic? That will produce the right answer in all cases, even when len + align overflows.
| ptr: Address, | ||
| layout: Layout, | ||
| new_layout: Layout) -> Result<Excess, AllocErr> { | ||
| let usable_size = self.usable_size(&new_layout); |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 1, 2017
Contributor
One other possible default implementation would be to copy the realloc default implementation, but use alloc_excess instead of alloc. This would work better if the default realloc algorithm were used (and alloc_excess was more precise than usable_size). However, I don't think it's worth it, since people are probably more likely to just override realloc and forget about realloc_excess.
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 1, 2017
Author
Member
yeah I think this would only win if you were hooking in an allocator that both
- overrides
fn alloc_excess, and - does not override
fn realloc_excess.
Probably better to just advise allocator implementators to understand what the default implementations do when deciding which methods to override.
F001
reviewed
Jun 1, 2017
| if usable >= new_layout.size() { Ok(()) } else { Err(CannotReallocInPlace) } | ||
| } | ||
|
|
||
| unsafe fn alloc_unchecked(&mut self, layout: Layout) -> Option<*mut u8> { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 1, 2017
Author
Member
the early RFC drafts used NonZero, but there was concern about exposing that as part of the API for this type. See e.g. aturon's comment here
This comment has been minimized.
This comment has been minimized.
|
@gereeter wrote:
No, I just forgot to incorporate it into this draft. But it is on the checklist on the description for #32838 and I'll try to put it in while I address the other great feedback you have posted here. |
pnkfelix
force-pushed the
pnkfelix:allocator-integration
branch
from
f21b714
to
d5baa4e
Jun 1, 2017
This comment has been minimized.
This comment has been minimized.
|
@sfackler @alexcrichton @gereeter any of you want to be official reviewer here? |
pnkfelix
added
S-waiting-on-review
and removed
S-waiting-on-author
labels
Jun 2, 2017
alexcrichton
assigned
alexcrichton
and unassigned
aturon
Jun 2, 2017
This comment has been minimized.
This comment has been minimized.
|
Sure, I don't mind reviewing! I read over this and the high-level comments I had were:
Some API nits I also had are below. I'm ambivalent about whether we must resolve these questions before landing.
|
This comment has been minimized.
This comment has been minimized.
To quote the RFC text:
Returning More minorly, I'm a little worried about efficiency - I see the in-place methods as quick checks that are done first, mostly, and so those don't want a complex error reporting system.
I may be misunderstanding what you mean here, but the amount of padding you need to hit a given alignment will increase with the alignment of the parameter - e.g., an
So, this is interesting because reading your comment I realized that my intuition was wrong. As far as I understand it, the minimum returned from The reasonable case I can think of is in something like See also the motivation comment for including the minimum. |
gereeter
suggested changes
Jun 3, 2017
| if let Ok(()) = self.grow_in_place(ptr, layout.clone(), new_layout.clone()) { | ||
| return Ok(ptr); | ||
| } | ||
| } else if new_size < old_size { |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 3, 2017
Contributor
It seems unfortunate that if new_size == old_size then this always allocates (when allocating is never necessary). Presumably either one of these two cases should cover that (my vote) or there should be a final else branch.
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 5, 2017
Author
Member
Oh yeah, good point. I know I was also non-plussed by the handling of same size inputs, but cannot remember why I didn't just resolve it in the manner you suggest. (Apart from just being unused to the new grow/shrink methods and possibly forgetting if they required the new size to be strictly greater/less than the old one...)
| } | ||
| } | ||
|
|
||
| /// The `CannotReallocInPlace` error is used when `fn realloc_in_place` |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 3, 2017
Contributor
Regardless of style, realloc_in_place has now been replaced by grow_in_place and shrink_in_place.
| } | ||
| } | ||
|
|
||
| unsafe fn grow_in_place(&mut self, |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 3, 2017
Contributor
I think the heap allocator should have an identical implementation of shrink_in_place?
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 5, 2017
Author
Member
Hmm I had thought that there was some reason that the spec for reallocate_inplace made it hard to detect error. But now I don't know exactly why I had reached that conclusion. Will review.
| pub struct HeapAllocator; | ||
|
|
||
| unsafe impl Allocator for HeapAllocator { | ||
| unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> { |
This comment has been minimized.
This comment has been minimized.
| new_layout: Layout) -> Result<(), CannotReallocInPlace> { | ||
| let _ = ptr; // this default implementation doesn't care about the actual address. | ||
| debug_assert!(new_layout.size <= layout.size); | ||
| if new_layout.align != layout.align { |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 3, 2017
Contributor
Can we just require that new_layout.align == layout.align for defined behavior? I can't see that this would be hard for users to follow, and it makes sense to me (obviously you won't change the alignment if you don't change the pointer).
This would require adding the check in the default implementation of realloc, but that doesn't seem to be a problem.
We could just only require that new_layout.align <= layout.align, but I don't see that being useful for users, and it might be annoying for allocator implementations, since different alignments might result in different size classes.
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 5, 2017
Author
Member
I don't have a problem with requiring the old and new alignments to be the same.
| // makes it hard to detect failure if it does not hold. | ||
| debug_assert!(new_layout.size() >= layout.size()); | ||
|
|
||
| if layout.align() != new_layout.align() { // reallocate_inplace requires this. |
This comment has been minimized.
This comment has been minimized.
gereeter
Jun 3, 2017
Contributor
At the very least, the comment here seems incorrect. reallocate_inplace doesn't even see new_layout.align().
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 5, 2017
Author
Member
... but that's exactly the issue. reallocate_inplace doesn't take enough inputs to deal with distinct old and new alignments. It just says that its required to take the old alignment as its align parameter, and that it will produce a block that is aligned according to that.
So if you give this method distinct alignments for old and new, then reallocate_inplace is not going to be able to satisfy the request.
This comment has been minimized.
This comment has been minimized.
|
@alexcrichton I'll add todo's corresponding to your high-level comments to the PR description.
|
This comment has been minimized.
This comment has been minimized.
|
(continuing previous comment responding to @alexcrichton 's points...)
|
pnkfelix
added
S-waiting-on-author
and removed
S-waiting-on-review
labels
Jun 5, 2017
This comment has been minimized.
This comment has been minimized.
Hmm. While I personally think an inclusive bound is easy for clients, an exclusive bound would probably be fine for clients too, and would probably be less annoying for implementors... The point about returning 0 is perhaps the only one in favor of keeping the bound inclusive, but so much of the API already says that you're not supposed to pass in layouts of zero-size... In any case, If we're going to revise this we should decide soon. Its the kind of corner-case that will be impossible to revise after the fact... |
This comment has been minimized.
This comment has been minimized.
|
Reviewing the API and documentation, I am reminded that the current This was an intuitive definition, and in practice it may be fine, but it may or may not be "the right thing" when it comes to the requirements of arbitrary allocators. In particular, it does not match the requirements of
I.e. this does not allow the @ruuda has been pointing out for quite a while (here and elsewhere) that this is a potential problem with both the Anyway, I point this out mostly to say that the current |
alexcrichton
referenced this pull request
Jun 5, 2017
Merged
Prepare global allocators for stabilization #1974
This comment has been minimized.
This comment has been minimized.
Thanks for the link! Gives me some extra fodder for global allocator traits.
That's a good point! My worry here, though, was that you'd do a lot of work to get a "no" on either the grow/shrink paths and then do more of the same work when you do the full reallocation. You later mentioned that it's pretty reasonable to not implement I'd be ok with this default implementation if we'd assume that allocators which support grow/shrink in place also reimplement
Ok, you're reasoning makes sense to me! If it's normal to fail I wonder if we could consider just returning a In any case this'll likely be an unstable API for awhile and it's otherwise just "one more error type" so it's not that bad either way, I was just curious to get some more rationale written out.
Ok all this about returning a minimum makes sense to me, thanks! The last API question I had was about The actual thing the function does makes total sense of me, I was basically just trying to rationalize why |
alexcrichton
reviewed
Jun 5, 2017
| /// padding required to get a 4-aligned address (assuming that the | ||
| /// corresponding memory block starts at a 4-aligned address). | ||
| /// | ||
| /// Behavior undefined if `align` is not a power-of-two. |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 5, 2017
Member
This may be a bit strong for this method, specifically "behavior undefined" in the sense that this method isn't unsafe so it's technically not allowed to do undefined things for any input. Perhaps rewording this to something like:
The return value of this function has no meaning if
alignis not a power-of-two.
or something like that?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 6, 2017
Author
Member
The return value of this function has no meaning if
alignis not a power-of-two.
I agree this is a more appropriate way to phrase this.
This comment has been minimized.
This comment has been minimized.
joshlf
Jun 6, 2017
Contributor
From the internal comment:
align is guaranteed to be > 0, so align - 1 is always valid.
So maybe instead have the sentence be:
The return value of this function has no meaning if
alignis not a positive power of two
(also, stylistically, I think it should be "power of two" rather than "power-of-two" (the latter would be appropriate if it were used as an adjective, e.g. "this type has a power-of-two alignment"))
Any reason not to just
assert!(align > 0)?
Please ignore me; I really don't know what I was thinking when I wrote that
This comment has been minimized.
This comment has been minimized.
|
Also FWIW I'm sort of trying to head off differences between this and the global allocators RFC which is currently leaning on a custom trait that's relatively different from this one. I'd personally prefer to stick to one |
This comment has been minimized.
This comment has been minimized.
I've been working on a crate for a while now that implements a Slab Allocator (among others). It is a single-threaded algorithm, and thus all of my methods are
(side note: I'm not ready to publish the crate yet, so it's still in a private repo, but I'd be happy to add anybody who was curious to take a look) |
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
@bors: r+ |
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
Jun 19, 2017
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
@bors retry timeout on os x builders |
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
Jun 20, 2017
This comment has been minimized.
This comment has been minimized.
|
|
bors
merged commit 55a629d
into
rust-lang:master
Jun 20, 2017
This was referenced Jun 20, 2017
This was referenced Jun 21, 2017
csssuf
added a commit
to csssuf/rust-uefi
that referenced
this pull request
Jun 21, 2017
alexbool
referenced this pull request
Jun 21, 2017
Closed
Binary size blowup presumably because of allocator integration #42808
csssuf
added a commit
to csssuf/rust-uefi
that referenced
this pull request
Jun 21, 2017
csssuf
added a commit
to csssuf/rust-uefi
that referenced
this pull request
Jun 21, 2017
csssuf
added a commit
to csssuf/rust-uefi
that referenced
this pull request
Jun 21, 2017
csssuf
added a commit
to csssuf/rust-uefi
that referenced
this pull request
Jul 6, 2017
This comment has been minimized.
This comment has been minimized.
|
Is there a reason that |
This comment has been minimized.
This comment has been minimized.
|
@joshlf Well, a reason More succinctly: I didn't think it was a good idea to build in that sort of constraint, especially for a type that I suspect is going to be somewhat niche. |
This comment has been minimized.
This comment has been minimized.
|
@pnkfelix Fair enough; that makes sense. |
This comment has been minimized.
This comment has been minimized.
|
@pnkfelix Actually, a follow-up on this. What I've been doing in my code is just cloning |
pnkfelix commentedMay 30, 2017
•
edited
Lets start getting some feedback on
trait Alloc.Here is:
trait Allocitself,struct Layoutandenum AllocErrthat its API relies onstruct HeapAllocthat exposes the system allocator as an instance ofAllocAllocwithRawVecan integration ofAllocwithVecTODO
fn realloc_in_placeintogrowandshrinkvariants# Unsafetyand# Errorssections to documentation for all relevant methodsVecintegration withAllocatorallocate_zeroedimpl toHeapAllocatortype Size = usize;trait Errorfor all error types in PRLayout::from_size_alignpublicfn padding_needed_for.Layoutconstructors to ensure that size+align combination is valid