From 80740bac94ffb03603ed9b93fef8f4c037615cab Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Tue, 1 Dec 2015 17:35:28 +0100 Subject: [PATCH 01/37] Allocators, take III, at long last. --- text/0000-kinds-of-allocators.md | 2061 ++++++++++++++++++++++++++++++ 1 file changed, 2061 insertions(+) create mode 100644 text/0000-kinds-of-allocators.md diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md new file mode 100644 index 00000000000..4cbff5c22a3 --- /dev/null +++ b/text/0000-kinds-of-allocators.md @@ -0,0 +1,2061 @@ +- Feature Name: allocator_api +- Start Date: 2015-12-01 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Add a standard allocator interface and support for user-defined +allocators, with the following goals: + + 1. Allow libraries (in libstd and elsewhere) to be generic with + respect to the particular allocator, to support distinct, + stateful, per-container allocators. + + 2. Require clients to supply metadata (such as block size and + alignment) at the allocation and deallocation sites, to ensure + hot-paths are as efficient as possible. + + 3. Provide high-level abstraction over the layout of an object in + memory. + +Regarding GC: We plan to allow future allocators to integrate +themselves with a standardized reflective GC interface, but leave +specification of such integration for a later RFC. (The design +describes a way to add such a feature in the future while ensuring +that clients do not accidentally opt-in and risk unsound behavior.) + +# Motivation +[motivation]: #motivation + +As noted in [RFC PR 39][] (and reiterated in [RFC PR 244][]), modern general purpose allocators are good, +but due to the design tradeoffs they must make, cannot be optimal in +all contexts. (It is worthwhile to also read discussion of this claim +in papers such as +[Reconsidering Custom Malloc](#reconsidering-custom-memory-allocation).) + +Therefore, the standard library should allow clients to plug in their +own allocator for managing memory. + +## Allocators are used in C++ system programming + +The typical reasons given for use of custom allocators in C++ are among the +following: + + 1. Speed: A custom allocator can be tailored to the particular + memory usage profiles of one client. This can yield advantages + such as: + + * A bump-pointer based allocator, when available, is faster + than calling `malloc`. + + * Adding memory padding can reduce/eliminate false sharing of + cache lines. + + 2. Stability: By segregating different sub-allocators and imposing + hard memory limits upon them, one has a better chance of handling + out-of-memory conditions. + + If everything comes from a single global heap, it becomes much + harder to handle out-of-memory conditions because by the time the + handler runs, it is almost certainly going to be unable to + allocate any memory for its own work. + + 3. Instrumentation and debugging: One can swap in a custom + allocator that collects data such as number of allocations, + or time for requests to be serviced. + +## Allocators should feel "rustic" + +In addition, for Rust we want an allocator API design that leverages +the core type machinery and language idioms (e.g. using `Result`, with +a `NonZero` okay variant and a zero-sized error variant), and provides +premade functions for common patterns for allocator clients (such as +allocating either single instances of a type, or arrays of some types +of dynamically-determined length). + +## Garbage Collection integration + +Finally, we want our allocator design to allow for a garbage +collection (GC) interface to be added in the future. + +At the very least, we do not want to accidentally *disallow* GC by +choosing an allocator API that is fundamentally incompatible with it. + +(However, this RFC does not actually propose a concrete solution for +how to integrate allocators with GC.) + +# Detailed design +[design]: #detailed-design + +## The `Allocator` trait at a glance + +The source code for the `Allocator` trait prototype ks provided in an +[appendix][Source for Allocator]. But since that section is long, here +we summarize the high-level points of the `Allocator` API. + +(See also the [walk thru][] section, which actually links to +individual sections of code.) + + * Basic implementation of the trait requires just two methods + (`alloc` and `dealloc`). You can get an initial implemention off + the ground with relatively little effort. + + * All methods that can fail to satisfy a request return a `Result` + (rather than building in an assumption that they panic or abort). + + * Furthermore, allocator implementations are discouraged from + directly panicking or aborting on out-of-memory (OOM) during + calls to allocation methods; instead, + clients that do wish to report that OOM occurred via a particular + allocator can do so via the `Allocator::oom()` method. + + * OOM is not the only type of error that may occur in general; + allocators can inject more specific error types to indicate + why an allocation failed. + + * The metadata for any allocation is captured in a `Kind` + abstraction. This type carries (at minimum) the size and alignment + requirements for a memory request. + + * The `Kind` type provides a large family of functional construction + methods for building up the description of how memory is laid out. + + * Any sized type `T` can be mapped to its `Kind`, via `Kind::new::()`, + + * Heterogenous structure; e.g. `kind1.extend(kind2)`, + + * Homogenous array types: `kind.repeat(n)` (for `n: usize`), + + * There are packed and unpacked variants for the latter two methods. + + * Helper `Allocator` methods like `fn alloc_one` and `fn + alloc_array` allow client code to interact with an allocator + without ever directly constructing a `Kind`. + + * Once an `Allocator` implementor has the `fn alloc` and `fn dealloc` + methods working, it can provide overrides of the other methods, + providing hooks that take advantage of specific details of how your + allocator is working underneath the hood. + + * In particular, the interface provides a few ways to let clients + potentially reuse excess memory associated with a block + + * `fn realloc` is a common pattern (where the client hopes that + the method will reuse the original memory when satisfying the + `realloc` request). + + * `fn alloc_excess` and `fn usable_size` provide an alternative + pattern, where your allocator tells the client about the excess + memory provided to satisfy a request, and the client can directly + expand into that excess memory, without doing round-trip requests + through the allocator itself. + +## Semantics of allocators and their memory blocks +[semantics of allocators]: #semantics-of-allocators-and-their-memory-blocks + +In general, an allocator provide access to a memory pool that owns +some amount of backing storage. The pool carves off chunks of that +storage and hands it out, via the allocator, as individual blocks of +memory to service client requests. (A "client" here is usually some +container library, like `Vec` or `HashMap`, that has been suitably +parameterized so that it has an `A:Allocator` type parameter.) + +So, an interaction between a program, a collection library, and an +allocator might look like this: + + +If you cannot see the SVG linked here, try the [ASCII art version][ascii-art] appendix. +Also, if you have suggestions for changes to the SVG, feel free to write them as a comment +in that appendix; (but be sure to be clear that you are pointing out a suggestion for the SVG). + + +In general, an allocator might be the backing memory pool itself; or +an allocator might merely be a *handle* that references the memory +pool. In the former case, when the allocator goes out of scope or is +otherwise dropped, the memory pool is dropped as well; in the latter +case, dropping the allocator has no effect on the memory pool. + + * One allocator that acts as a handle is the global heap allocator, + whose associated pool is the low-level `#[allocator]` crate. + + * Another allocator that acts as a handle is a `&'a Pool`, where + `Pool` is some structure implementing a sharable backing store. + The big [example][] section shows an instance of this. + + * An allocator that is its own memory pool would be a type + analogous to `Pool` that implements the `Allocator` interface + directly, rather than via `&'a Pool`. + + * A case in the middle of the two extremes might be something like an + allocator of the form `Rc>`. This reflects *shared* + ownership between a collection of allocators handles: dropping one + handle will not drop the pool as long as at least one other handle + remains, but dropping the last handle will drop the pool itself. + +A client that is generic over all possible `A:Allocator` instances +cannot know which of the above cases it falls in. This has consequences +in terms of the restrictions that must be met by client code +interfacing with an allocator, which we discuss in a +later [section on lifetimes][lifetimes]. + + +## Example Usage +[example]: #example-usage + +Lets jump into a demo. Here is a (super-dumb) bump-allocator that uses +the `Allocator` trait. + +### Implementing the `Allocator` trait + +First, the bump-allocator definition itself: each such allocator will +have its own name (for error reports from OOM), start and limit +pointers (`ptr` and `end`, respectively) to the backing storage it is +allocating into, as well as the byte alignment (`align`) of that +storage, and an `avail: AtomicPtr` for the cursor tracking how +much we have allocated from the backing storage. +(The `avail` field is an atomic because eventually we want to try +sharing this demo allocator across scoped threads.) + +```rust +struct DumbBumpPool { + name: &'static str, + ptr: *mut u8, + end: *mut u8, + avail: AtomicPtr, + align: usize, +} +``` + +The initial implementation is pretty straight forward: just immediately +allocate the whole pool's backing storage. + +(If we wanted to be really clever we might layer this type on top of +*another* allocator. +For this demo I want to try to minimize cleverness, so we will use +`heap::allocate` to grab the backing storage instead of taking an +`Allocator` of our own.) + + +```rust +impl DumbBumpPool { + fn new(name: &'static str, + size_in_bytes: usize, + start_align: usize) -> DumbBumpPool { + unsafe { + let ptr = heap::allocate(size_in_bytes, start_align); + if ptr.is_null() { panic!("allocation failed."); } + let end = ptr.offset(size_in_bytes as isize); + DumbBumpPool { + name: name, + ptr: ptr, end: end, avail: AtomicPtr::new(ptr), + align: start_align + } + } + } +} +``` + +Since clients are not allowed to have blocks that outlive their +associated allocator (see the [lifetimes][] section), +it is sound for us to always drop the backing storage for an allocator +when the allocator itself is dropped +(regardless of what sequence of `alloc`/`dealloc` interactions occured +with the allocator's clients). + +```rust +impl Drop for DumbBumpPool { + fn drop(&mut self) { + unsafe { + let size = self.end as usize - self.ptr as usize; + heap::deallocate(self.ptr, size, self.align); + } + } +} +``` + +Now, before we get into the trait implementation itself, here is an +interesting simple design choice: + + * To show-off the error abstraction in the API, we make a special + error type that covers a third case that is not part of the + standard `enum AllocErr`. + +Specifically, our bump allocator has *three* error conditions that we +will expose: + + 1. the inputs could be invalid, + + 2. the memory could be exhausted, or, + + 3. there could be *interference* between two threads. + This latter scenario means that this allocator failed + on this memory request, but the client might + quite reasonably just *retry* the request. + +```rust +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +enum BumpAllocError { Invalid, MemoryExhausted, Interference } + +impl alloc::AllocError for BumpAllocError { + fn invalid_input() -> Self { BumpAllocError::MemoryExhausted } + fn is_memory_exhausted(&self) -> bool { *self == BumpAllocError::MemoryExhausted } + fn is_request_unsupported(&self) -> bool { false } + fn is_transient(&self) { *self == BumpAllocError::Interference } +} +``` + +With that out of the way, here are some other design choices of note: + + * Our Bump Allocator is going to use a most simple-minded deallocation + policy: calls to `fn dealloc` are no-ops. Instead, every request takes + up fresh space in the backing storage, until the pool is exhausted. + (This was one reason I use the word "Dumb" in its name.) + + * Since we want to be able to share the bump-allocator amongst multiple + (lifetime-scoped) threads, we will implement the `Allocator` interface + as a *handle* pointing to the pool; in this case, a simple reference. + +Here is the demo implementation of `Allocator` for the type. + +```rust +impl<'a> Allocator for &'a DumbBumpPool { + type Kind = alloc::Kind; + type Error = BumpAllocError; + + unsafe fn alloc(&mut self, kind: &Self::Kind) -> Result { + let curr = self.avail.load(Ordering::Relaxed) as usize; + let align = *kind.align(); + let curr_aligned = (curr.overflowing_add(align - 1)) & !(align - 1); + let size = *kind.size(); + let remaining = (self.end as usize) - curr_aligned; + if remaining <= size { + return Err(BumpAllocError::MemoryExhausted); + } + + let curr = curr as *mut u8; + let curr_aligned = curr_aligned as *mut u8; + let new_curr = curr_aligned.offset(size as isize); + + if curr != self.avail.compare_and_swap(curr, new_curr, Ordering::Relaxed) { + return Err(BumpAllocError::Interference); + } else { + println!("alloc finis ok: 0x{:x} size: {}", curr_aligned as usize, size); + return Ok(NonZero::new(curr_aligned)); + } + } + + unsafe fn dealloc(&mut self, _ptr: Address, _kind: &Self::Kind) -> Result<(), Self::Error> { + // this bump-allocator just no-op's on dealloc + Ok(()) + } + + unsafe fn oom(&mut self) -> ! { + panic!("exhausted memory in {}", self.name); + } + +} +``` + +(Niko Matsakis has pointed out that this particular allocator might +avoid interference errors by using fetch-and-add rather than +compare-and-swap. The devil's in the details as to how one might +accomplish that while still properly adjusting for alignment; in any +case, the overall point still holds in cases outside of this specific +demo.) + +And that is it; we are done with our allocator implementation. + +### Using an `A:Allocator` from the client side + +We assume that `Vec` has been extended with a `new_in` method that +takes an allocator argument that it uses to satisfy its allocation +requests. + +```rust +fn demo_alloc(a1:A1, a2: A2, print_state: F) { + let mut v1 = Vec::new_in(a1); + let mut v2 = Vec::new_in(a2); + println!("demo_alloc, v1; {:?} v2: {:?}", v1, v2); + for i in 0..10 { + v1.push(i as u64 * 1000); + v2.push(i as u8); + v2.push(i as u8); + } + println!("demo_alloc, v1; {:?} v2: {:?}", v1, v2); + print_state(); + for i in 10..100 { + v1.push(i as u64 * 1000); + v2.push(i as u8); + v2.push(i as u8); + } + println!("demo_alloc, v1.len: {} v2.len: {}", v1.len(), v2.len()); + print_state(); + for i in 100..1000 { + v1.push(i as u64 * 1000); + v2.push(i as u8); + v2.push(i as u8); + } + println!("demo_alloc, v1.len: {} v2.len: {}", v1.len(), v2.len()); + print_state(); +} + +fn main() { + use std::thread::catch_panic; + + if let Err(panicked) = catch_panic(|| { + let alloc = DumbBumpPool::new("demo-bump", 4096, 1); + demo_alloc(&alloc, &alloc, || println!("alloc: {:?}", alloc)); + }) { + match panicked.downcast_ref::() { + Some(msg) => { + println!("DumbBumpPool panicked: {}", msg); + } + None => { + println!("DumbBumpPool panicked"); + } + } + } + + // // The below will be (rightly) rejected by compiler when + // // all pieces are properly in place: It is not valid to + // // have the vector outlive the borrowed allocator it is + // // referencing. + // + // let v = { + // let alloc = DumbBumpPool::new("demo2", 4096, 1); + // let mut v = Vec::new_in(&alloc); + // for i in 1..4 { v.push(i); } + // v + // }; + + let alloc = DumbBumpPool::new("demo-bump", 4096, 1); + for i in 0..100 { + let r = ::std::thread::scoped(|| { + let v = Vec::new_in(&alloc); + for j in 0..10 { + v.push(j); + } + }); + } + + println!("got here"); +} +``` + +And that's all to the demo, folks. + +## Allocators and lifetimes +[lifetimes]: #allocators-and-lifetimes + +As mentioned above, allocators provide access to a memory pool. An +allocator can *be* the pool (in the sense that the allocator owns the +backing storage that represents the memory blocks it hands out), or an +allocator can just be a handle that points at the pool. + +Some pools have indefinite extent. An example of this is the global +heap allocator, requesting memory directly from the low-level +`#[allocator]` crate. Clients of an allocator with such a pool need +not think about how long the allocator lives; instead, they can just +freely allocate blocks, use them at will, and deallocate them at +arbitrary points in the future. Memory blocks that come from such a +pool will leak if it is not explicitly deallocated. + +Other pools have limited extent: they are created, they build up +infrastructure to manage their blocks of memory, and at some point, +such pools are torn down. Memory blocks from such a pool may or may +not be returned to the operating system during that tearing down. + +There is an immediate question for clients of an allocator with the +latter kind of pool (i.e. one of limited extent): whether it should +attempt to spend time deallocating such blocks, and if so, at what +time to do so? + +Again, note: + + * generic clients (i.e. that accept any `A:Allocator`) *cannot know* + what kind of pool they have, or how it relates to the allocator it + is given, + + * dropping the client's allocator may or may not imply the dropping + of the pool itself! + +That is, code written to a specific `Allocator` implementation may be +able to make assumptions about the relationship between the memory +blocks and the allocator(s), but the generic code we expect the +standard library to provide cannot make such assumptions. + +To satisfy the above scenarios in a sane, consistent, general fashion, +the `Allocator` trait assumes/requires all of the following: + + 1. (for allocator impls and clients): in the absence of other + information (e.g. specific allocator implementations), all blocks + from a given pool have lifetime equivalent to the lifetime of the + pool. + + This implies if a client is going to read from, write to, or + otherwise manipulate a memory block, the client *must* do so before + its associated pool is torn down. + + (It also implies the converse: if a client can prove that the pool + for an allocator is still alive, then it can continue to work + with a memory block from that allocator even after the allocator + is dropped.) + + 2. (for allocator impls): an allocator *must not* outlive its + associated pool. + + All clients can assume this in their code. + + (This constraint provides generic clients the preconditions they + need to satisfy the first condition. In particular, even though + clients do not generally know what kind of pool is associated with + its allocator, it can conservatively assume that all blocks will + live at least as long as the allocator itself.) + + 3. (for allocator impls and clients): all clients of an allocator + *should* eventually call the `dealloc` method on every block they + want freed (otherwise, memory may leak). + + However, allocator implementations *must* remain sound even if + this condition is not met: If `dealloc` is not invoked for all + blocks and this condition is somehow detected, then an allocator + can panic (or otherwise signal failure), but that sole violation + must not cause undefined behavior. + + (This constraint is to encourage generic client authors to write + code that will not leak memory when instantiated with allocators + of indefinite extent, such as the global heap allocator.) + + 4. (for allocator impls): moving an allocator value *must not* + invalidate its outstanding memory blocks. + + All clients can assume this in their code. + + So if a client allocates a block from an allocator (call it `a1`) + and then `a1` moves to a new place (e.g. via`let a2 = a1;`), then + it remains sound for the client to deallocate that block via + `a2`. + + Note that this implies that it is not sound to implement an + allocator that embeds its own pool structurally inline. + + E.g. this is *not* a legal allocator: + ```rust + struct MegaEmbedded { pool: [u8; 1024*1024], cursor: usize, ... } + impl Allocator for MegaEmbedded { ... } + ``` + The latter impl is simply unreasonable (at least if one is + intending to satisfy requests by returning pointers into + `self.bytes`). + + (Note of course, `impl Allocator for &mut MegaEmbedded` is in + principle *fine*; that would then be an allocator that is an + indirect handle to an unembedded pool.) + + 5. (for allocator impls and clients) if an allocator is cloneable, the + client *can assume* that all clones + are interchangably compatible in terms of their memory blocks: if + allocator `a2` is a clone of `a1`, then one can allocate a block + from `a1` and return it to `a2`, or vice versa, or use `a2.realloc` + on the block, et cetera. + + This essentially means that any cloneable + allocator *must* be a handle indirectly referencing a pool of some + sort. (Though do remember that such handles can collectively share + ownership of their pool, such as illustrated in the + `Rc>` example given earlier.) + + (Note: one might be tempted to further conclude that this also + implies that allocators implementing `Copy` must have pools of + indefinite extent. While this seems reasonable for Rust as it + stands today, I am slightly worried whether it would continue to + hold e.g. in a future version of Rust with something like + `Gc: Copy`, where the `GcPool` and its blocks is reclaimed + (via finalization) sometime after being determined to be globally + unreachable. Then again, perhaps it would be better to simply say + "we will not support that use case for the allocator API", so that + clients would be able to employ the reasoning outlined in the + outset of this paragraph.) + + +## A walk through the Allocator trait +[walk thru]: #a-walk-through-the-allocator-trait + +### Role-Based Type Aliases + +Allocation code often needs to deal with values that boil down to a +`usize` in the end. But there are distinct roles (e.g. "size", +"alignment") that such values play, and I decided those roles would be +worth hard-coding into the method signatures. + + * Therefore, I made [type aliases][] for `Size`, `Capacity`, `Alignment`, and `Address`. + +Furthermore, all values of the above types must be non-zero for any +allocation action to make sense. + + * Therefore, I made them instances of the `NonZero` type. + +### Basic implementation + +An instance of an allocator has many methods, but an implementor of +the trait need only provide two method bodies: [alloc and dealloc][]. + +(This is only *somewhat* analogous to the `Iterator` trait in Rust. It +is currently very uncommon to override any methods of `Iterator` ecept +for `fn next`. However, I expect it will be much more common for +`Allocator` to override at least some of the other methods, like `fn +realloc`.) + +The `alloc` method returns an `Address` when it succeeds, and +`dealloc` takes such an address as its input. But the client must also +provide metadata for the allocated block like its size and alignment. +This is encapsulated in the `Kind` argument to `alloc` and `dealloc`. + +### Kinds of allocations + +A `Kind` just carries the metadata necessary for satisfying an +allocation request. Its (current, private) representation is just a +size and alignment. + +The more interesting thing about `Kind` is the +family of public methods associated with it for building new kinds via +composition; these are shown in the [kind api][]. + +### Reallocation Methods + +Of course, real-world allocation often needs more than just +`alloc`/`dealloc`: in particular, one often wants to avoid extra +copying if the existing block of memory can be conceptually expanded +in place to meet new allocation needs. In other words, we want +`realloc`, plus alternatives to it that allow clients to avoid +round-tripping through the allocator API. + +For this, the [memory reuse][] family of methods is appropriate. + +### Type-based Helper Methods + +Some readers might skim over the `Kind` API and immediately say "yuck, +all I wanted to do was allocate some nodes for a tree-structure and +let my clients choose how the backing memory is chosen! Why do I have +to wrestle with this `Kind` business?" + +I agree with the sentiment; that's why the `Allocator` trait provides +a family of methods capturing [common usage patterns][]. + +## Unchecked variants + +Finally, all of the methods above return `Result`, and guarantee some +amount of input validation. (This is largely because I observed code +duplication doing such validation on the client side; or worse, such +validation accidentally missing.) + +However, some clients will want to bypass such checks (and do it +without risking undefined behavior by ensuring the preconditions hold +via local invariants in their container type). + +For these clients, the `Allocator` trait provides +["unchecked" variants][unchecked variants] of nearly all of its +methods. + +The idea here is that `Allocator` implementors are encouraged +to streamline the implmentations of such methods by assuming that all +of the preconditions hold. + + * However, to ease initial `impl Allocator` development for a given + type, all of the unchecked methods have default implementations + that call out to their checked counterparts. + + * (In other words, "unchecked" is in some sense a privilege being + offered to impl's; but there is no guarantee that an arbitrary impl + takes advantage of the privilege.) + +## Why this API +[Why this API]: #why-this-api + +Here are some quick points about how this API was selected + +### Why not just `free(ptr)` for deallocation? + +As noted in [RFC PR 39][] (and reiterated in [RFC PR 244][]), the basic `malloc` interface +{`malloc(size) -> ptr`, `free(ptr)`, `realloc(ptr, size) -> ptr`} is +lacking in a number of ways: `malloc` lacks the ability to request a +particular alignment, and `realloc` lacks the ability to express a +copy-free "reuse the input, or do nothing at all" request. Another +problem with the `malloc` interface is that it burdens the allocator +with tracking the sizes of allocated data and re-extracting the +allocated size from the `ptr` in `free` and `realloc` calls (the +latter can be very cheap, but there is still no reason to pay that +cost in a language like Rust where the relevant size is often already +immediately available as a compile-time constant). + +Therefore, in the name of (potential best-case) speed, we want to +require client code to provide the metadata like size and alignment +to both the allocation and deallocation call sites. + +### Why not just `alloc`/`dealloc` (or `alloc`/`dealloc`/`realloc`)? + +* The `alloc_one`/`dealloc_one` and `alloc_array`/`dealloc_array` + capture a very common pattern for allocation of memory blocks where + a simple value or array type is being allocated. + +* The `alloc_array_unchecked` and `dealloc_array_unchecked` likewise + capture a similar pattern, but are "less safe" in that they put more + of an onus on the caller to validate the input parameters before + calling the methods. + +* The `alloc_excess` and `realloc_excess` methods provide a way for + callers who can make use of excess memory to avoid unnecessary calls + to `realloc`. + + +### Why `alloc_array_unchecked` and `dealloc_array_unchecked`? + +### Why the `Kind` abstraction? + +While we do want to require clients to hand the allocator the size and +alignment, we have found that the code to compute such things follows +regular patterns. It makes more sense to factor those patterns out +into a common abstraction; this is what `Kind` provides: a high-level +API for describing the memory layout of a composite structure by +composing the layout of its subparts. + +### Why return `Result` rather than a raw pointer? + +My hypothesis is that the standard allocator API should embrace +`Result` as the standard way for describing local error conditions in +Rust. + +In principle, we can use `Result` without adding *any* additional +overhead (at least in terms of the size of the values being returned +from the allocation calls), because the error type for the `Result` +can be zero-sized if so desired. That is why the error is an +associated type of the `Allocator`: allocators that want to ensure the +results have minimum size can use the zero-sized `RequestUnsatisfied` +or `MemoryExhausted` types as their associated `Self::Error`. + + * `RequestUnsatisfied` is a catch-all type that any allocator + could use as its error type; doing so provides no hint to the + client as to what they could do to try to service future memory + requests. + + * `MemoryExhausted` is a specific error type meant for allocators + that could in principle handle *any* sane input request, if there + were sufficient memory available. (By "sane" we mean for example + that the input arguments do not cause an arithmetic overflow during + computation of the size of the memory block -- if they do, then it + is reasonable for an allocator with this error type to respond that + insufficent memory was available, rather than e.g. panicking.) + +### Why return `Result` rather than directly `oom` on failure + +Again, my hypothesis is that the standard allocator API should embrace +`Result` as the standard way for describing local error conditions in +Rust. + +I want to leave it up to the clients to decide if they can respond to +out-of-memory (OOM) conditions on allocation failure. + +However, since I also suspect that some programs would benefit from +contextual information about *which* allocator is reporting memory +exhaustion, I have made `oom` a method of the `Allocator` trait, so +that allocator clients can just call that on error (assuming they want +to trust the failure behavior of the allocator). + +### Why is `usable_size` ever needed? Why not call `kind.size()` directly, as is done in the default implementation? + +`kind.size()` returns the minimum required size that the client needs. +In a block-based allocator, this may be less than the *actual* size +that the allocator would ever provide to satisfy that `kind` of +request. Therefore, `usable_size` provides a way for clients to +observe what the minimum actual size of an allocated block for +that`kind` would be, for a given allocator. + +(Note that the documentation does say that in general it is better for +clients to use `alloc_excess` and `realloc_excess` instead, if they +can, as a way to directly observe the *actual* amount of slop provided +by the particular allocator.) + +### Why is `Allocator` an `unsafe trait`? + +It just seems like a good idea given how much of the standard library +is going to assume that allocators are implemented according to their +specification. + +(I had thought that `unsafe fn` for the methods would suffice, but +that is putting the burden of proof (of soundness) in the *wrong* +direction...) + +## The GC integration strategy +[gc integration]: #the-gc-integration-strategy + +One of the main reasons that [RFC PR 39] was not merged as written +was because it did not account for garbage collection (GC). + +In particular, assuming that we eventually add support for GC in some +form, then any value that holds a reference to an object on the GC'ed +heap will need some linkage to the GC. In particular, if the *only* +such reference (i.e. the one with sole ownership) is held in a block +managed by a user-defined allocator, then we need to ensure that all +such references are found when the GC does its work. + +The Rust project has control over the `libstd` provided allocators, so +the team can adapt them as necessary to fit the needs of whatever GC +designs come around. But the same is not true for user-defined +allocators: we want to ensure that adding support for them does not +inadvertantly kill any chance for adding GC later. + +### The inspiration for Kind + +Some aspects of the design of this RFC were selected in the hopes that +it would make such integration easier. In particular, the introduction +of the relatively high-level `Kind` abstraction was developed, in +part, as a way that a GC-aware allocator would build up a tracing +method associated with a kind. + +Then I realized that the `Kind` abstraction may be valuable on its +own, without GC: It encapsulates important patterns when working with +representing data as memory records. + +So, this RFC offers the `Kind` abstraction without promising that it +solves the GC problem. (It might, or it might not; we don't know yet.) + +### Forwards-compatibility + +So what *is* the solution for forwards-compatibility? + +It is this: Rather than trying to build GC support into the +`Allocator` trait itself, we instead assume that when GC support +comes, it may come with a new trait (call it `GcAwareAllocator`). + + * (Perhaps we will instead use an attribute; the point is, whatever + option we choose can be incorporated into the meta-data for a + crate.) + +Allocators that are are GC-compatible have to explicitly declare +themselves as such, by implementing `GcAwareAllocator`, which will +then impose new conditions on the methods of `Allocator`, for example +ensuring e.g. that allocated blocks of memory can be scanned +(i.e. "parsed") by the GC (if that in fact ends up being necessary). + +This way, we can deploy an `Allocator` trait API today that does not +provide the necessary reflective hooks that a GC wuold need to access. + +Crates that define their own `Allocator` implementations without also +claiming them to be GC-compatible will be forbidden from linking with +crates that require GC support. (In other words, when GC support +comes, we assume that the linking component of the Rust compiler will +be extended to check such compatibility requirements.) + +# Drawbacks +[drawbacks]: #drawbacks + +The API may be over-engineered. + +The core set of methods (the ones without `unchecked`) return +`Result` and potentially impose unwanted input validation overhead. + + * The `_unchecked` variants are intended as the response to that, + for clients who take care to validate the many preconditions + themselves in order to minimize the allocation code paths. + +# Alternatives +[alternatives]: #alternatives + +## Just adopt [RFC PR 39][] with this RFC's GC strategy + +The GC-compatibility strategy described here (in [gc integration][]) +might work with a large number of alternative designs, such as that +from [RFC PR 39][]. + +While that is true, it seems like it would be a little short-sighted. +In particular, I have neither proven *nor* disproven the value of +`Kind` system described here with respect to GC integration. + +As far as I know, it is the closest thing we have to a workable system +for allowing client code of allocators to accurately describe the +layout of values they are planning to allocate, which is the main +ingredient I believe to be necessary for the kind of dynamic +reflection that a GC will require of a user-defined allocator. + +## Make `Kind` an associated type of `Allocator` trait + +I explored making an `AllocKind` bound and then having + +```rust +pub unsafe trait Allocator { + /// Describes the sort of records that this allocator can + /// construct. + type Kind: AllocKind; + + ... +} +``` + +Such a design might indeed be workable. (I found it awkward, which is +why I abandoned it.) + +But the question is: What benefit does it bring? + +The main one I could imagine is that it might allow us to introduce a +division, at the type-system level, between two kinds of allocators: +those that are integrated with the GC (i.e., have an associated +`Allocator::Kind` that ensures that all allocated blocks are scannable +by a GC) and allocators that are *not* integrated with the GC (i.e., +have an associated `Allocator::Kind` that makes no guarantees about +one will know how to scan the allocated blocks. + +However, no such design has proven itself to be "obviously feasible to +implement," and therefore it would be unreasonable to make the `Kind` +an associated type of the `Allocator` trait without having at least a +few motivating examples that *are* clearly feasible and useful. + +## Variations on the `Kind` API + + * Should `Kind` offer a `fn resize(&self, new_size: usize) -> Kind` constructor method? + (Such a method would rule out deriving GC tracers from kinds; but we could + maybe provide it as an `unsafe` method.) + + * Should `Kind` ensure an invariant that its associated size is + always a multiple of its alignment? + + * Doing this would allow simplifying a small part of the API, + namely the distinct `Kind::repeat` (returns both a kind and an + offset) versus `Kind::array` (where the offset is derivable from + the input `T`). + + * Such a constraint would have precendent; in particular, the + `aligned_alloc` function of C11 requires the given size + be a multiple of the alignment. + + * On the other hand, both the system and jemalloc allocators seem + to support more flexible allocation patterns. Imposing the above + invariant implies a certain loss of expressiveness over what we + already provide today. + + * Should `Kind` ensure an invariant that its associated size is always positive? + + * Pro: Removes something that allocators would need to check about + input kinds (the backing memory allocators will tend to require + that the input sizes are positive). + + * Con: Requiring positive size means that zero-sized types do not have an associated + `Kind`. That's not the end of the world, but it does make the `Kind` API slightly + less convenient (e.g. one cannot use `extend` with a zero-sized kind to + forcibly inject padding, because zero-sized kinds do not exist). + + * Should `Kind::align_to` add padding to the associated size? (Probably not; this would + make it impossible to express certain kinds of patteerns.) + + * Should the `Kind` methods that might "fail" return `Result` instead of `Option`? + +## Variations on the `Allocator` API + + * Should `Allocator::alloc` be safe instead of `unsafe fn`? + + * Clearly `fn dealloc` and `fn realloc` need to be `unsafe`, since + feeding in improper inputs could cause unsound behavior. But is + there any analogous input to `fn alloc` that could cause + unsoundness (assuming that the `Kind` struct enforces invariants + like "the associated size is non-zero")? + + * (I left it as `unsafe fn alloc` just to keep the API uniform with + `dealloc` and `realloc`.) + + * Should `Allocator::realloc` not require that `new_kind.align()` + evenly divide `kind.align()`? In particular, it is not too + expensive to check if the two kinds are not compatible, and fall + back on `alloc`/`dealloc` in that case. + + * Should `Allocator` not provide unchecked variants on `fn alloc`, + `fn realloc`, et cetera? (To me it seems having them does no harm, + apart from potentially misleading clients who do not read the + documentation about what scenarios yield undefined behavior. + + * Another option here would be to provide a `trait + UncheckedAllocator: Allocator` that carries the unchecked + methods, so that clients who require such micro-optimized paths + can ensure that their clients actually pass them an + implementation that has the checks omitted. + + * On the flip-side of the previous bullet, should `Allocator` provide + `fn alloc_one_unchecked` and `fn dealloc_one_unchecked` ? + I think the only check that such variants would elide would be that + `T` is not zero-sized; I'm not sure that's worth it. + (But the resulting uniformity of the whole API might shift the + balance to "worth it".) + +# Unresolved questions +[unresolved]: #unresolved-questions + + * Should `Kind` be an associated type of `Allocator` (see + [alternatives][] section for discussion). + (In fact, most of the "Variations correspond to potentially + unresolved questions.) + + * Should `dealloc` return a `Result` or not? (Under what + circumstances would we expect `dealloc` to fail in a manner worth + signalling? The main one I can think of is a transient failure, + which is why the documentation for that method spends so much time + discussing it.) + + * Are the type definitions for `Size`, `Capacity`, `Alignment`, and + `Address` an abuse of the `NonZero` type? (Or do we just need some + constructor for `NonZero` that asserts that the input is non-zero)? + + * Should `fn oom(&self)` take in more arguments (e.g. to allow the + client to provide more contextual information about the OOM + condition)? + + * Does `AllocError::is_transient` belong in this version of the API, + or should we wait to add it later? (I originally suspected that + libstd data types would want to make use of it, which would means + we should add it. However, in the absence of a concrete example + stdlib type that would use it, we may be better off removing `fn + is_transient` from this API (instead specifying that allocators + with such transient failures will block (i.e. loop and retry + internally), with the expectation that if a need for such an + allocator does arise, we will then represent the API extension + via a different trait (perhaps an extension trait of `Allocator`). + + * On that note, if we remove the `fn is_transient` method, should + we get rid of the `AllocError` bound entirely? Is the given set + of methods actually worth providing to all generic clients? + + (Keeping it seems very low cost to me; implementors can always opt + to use the `MemoryExhausted` error type, which is cheap. But my + intuition may be wrong.) + + * Do we need `Allocator::max_size` and `Allocator::max_align` ? + + * Should default impl of `Allocator::max_align` return `None`, or is + there more suitable default? (perhaps e.g. `PLATFORM_PAGE_SIZE`?) + + The previous allocator documentation provided by Daniel Micay + suggest that we should specify that behavior unspecified if + allocation is too large, but if that is the case, then we should + definitely provide some way to *observe* that threshold.) + + From what I can tell, we cannot currently assume that all + low-level allocators will behave well for large alignments. + See https://github.com/rust-lang/rust/issues/30170 + + +# Appendices + +## Bibliography +[Bibliography]: #bibliography + +### RFC Pull Request #39: Allocator trait +[RFC PR 39]: https://github.com/rust-lang/rfcs/pull/39/files + +Daniel Micay, 2014. RFC: Allocator trait. https://github.com/thestinger/rfcs/blob/ad4cdc2662cc3d29c3ee40ae5abbef599c336c66/active/0000-allocator-trait.md + +### RFC Pull Request #244: Allocator RFC, take II +[RFC PR 244]: https://github.com/rust-lang/rfcs/pull/244 + +Felix Klock, 2014, Allocator RFC, take II, https://github.com/pnkfelix/rfcs/blob/d3c6068e823f495ee241caa05d4782b16e5ef5d8/active/0000-allocator.md + +### Dynamic Storage Allocation: A Survey and Critical Review +Paul R. Wilson, Mark S. Johnstone, Michael Neely, and David Boles, 1995. [Dynamic Storage Allocation: A Survey and Critical Review](https://parasol.tamu.edu/~rwerger/Courses/689/spring2002/day-3-ParMemAlloc/papers/wilson95dynamic.pdf) ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps . Slightly modified version appears in Proceedings of 1995 International Workshop on Memory Management (IWMM '95), Kinross, Scotland, UK, September 27--29, 1995 Springer Verlag LNCS + +### Reconsidering custom memory allocation +[ReCustomMalloc]: http://dl.acm.org/citation.cfm?id=582421 + +Emery D. Berger, Benjamin G. Zorn, and Kathryn S. McKinley. 2002. [Reconsidering custom memory allocation][ReCustomMalloc]. In Proceedings of the 17th ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications (OOPSLA '02). + +### The memory fragmentation problem: solved? +[MemFragSolvedP]: http://dl.acm.org/citation.cfm?id=286864 + +Mark S. Johnstone and Paul R. Wilson. 1998. [The memory fragmentation problem: solved?][MemFragSolvedP]. In Proceedings of the 1st international symposium on Memory management (ISMM '98). + +### EASTL: Electronic Arts Standard Template Library +[EASTL]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html + +Paul Pedriana. 2007. [EASTL] -- Electronic Arts Standard Template Library. Document number: N2271=07-0131 + +### Towards a Better Allocator Model +[Halpern proposal]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1850.pdf + +Pablo Halpern. 2005. [Towards a Better Allocator Model][Halpern proposal]. Document number: N1850=05-0110 + +### Various allocators + +[jemalloc], [tcmalloc], [Hoard] + +[jemalloc]: http://www.canonware.com/jemalloc/ + +[tcmalloc]: http://goog-perftools.sourceforge.net/doc/tcmalloc.html + +[Hoard]: http://www.hoard.org/ + +[tracing garbage collector]: http://en.wikipedia.org/wiki/Tracing_garbage_collection + +[malloc/free]: http://en.wikipedia.org/wiki/C_dynamic_memory_allocation + +## ASCII art version of Allocator message sequence chart +[ascii-art]: #ascii-art-version-of-allocator-message-sequence-chart + +This is an ASCII art version of the SVG message sequence chart +from the [semantics of allocators] section. + +``` +Program Vec Allocator + || + || + +--------------- create allocator -------------------> ** (an allocator is born) + *| <------------ return allocator A ---------------------+ + || | + || | + +- create vec w/ &mut A -> ** (a vec is born) | + *| <------return vec V ------+ | + || | | + *------- push W_1 -------> *| | + | || | + | || | + | +--- allocate W array ---> *| + | | || + | | || + | | +---- (request system memory if necessary) + | | *| <-- ... + | | || + | *| <--- return *W block -----+ + | || | + | || | + *| <------- (return) -------+| | + || | | + +------- push W_2 -------->+| | + | || | + *| <------- (return) -------+| | + || | | + +------- push W_3 -------->+| | + | || | + *| <------- (return) -------+| | + || | | + +------- push W_4 -------->+| | + | || | + *| <------- (return) -------+| | + || | | + +------- push W_5 -------->+| | + | || | + | +---- realloc W array ---> *| + | | || + | | || + | | +---- (request system memory if necessary) + | | *| <-- ... + | | || + | *| <--- return *W block -----+ + *| <------- (return) -------+| | + || | | + || | | + . . . + . . . + . . . + || | | + || | | + || (end of Vec scope) | | + || | | + +------ drop Vec --------> *| | + | || (Vec destructor) | + | || | + | +---- dealloc W array --> *| + | | || + | | +---- (potentially return system memory) + | | *| <-- ... + | | || + | *| <------- (return) --------+ + *| <------- (return) --------+ | + || | + || | + || | + || (end of Allocator scope) | + || | + +------------------ drop Allocator ------------------> *| + | || + | |+---- (return any remaining associated memory) + | *| <-- ... + | || + *| <------------------ (return) -------------------------+ + || + || + . + . + . +``` + + +## Transcribed Source for Allocator trait API +[Source for Allocator]: #transcribed-source-for-allocator-trait-api + +Here is the whole source file for my prototype allocator API, +sub-divided roughly accordingly to functionality. + +(We start with the usual boilerplate...) + +```rust +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![unstable(feature = "allocator_api", + reason = "the precise API and guarantees it provides may be tweaked \ + slightly, especially to possibly take into account the \ + types being stored to make room for a future \ + tracing garbage collector", + issue = "27700")] + +use core::cmp; +use core::fmt; +use core::mem; +use core::nonzero::NonZero; +use core::ptr::{self, Unique}; + +``` + +### Type Aliases +[type aliases]: #type-aliases + +```rust +pub type Size = NonZero; +pub type Capacity = NonZero; +pub type Alignment = NonZero; + +pub type Address = NonZero<*mut u8>; + +/// Represents the combination of a starting address and +/// a total capacity of the returned block. +pub struct Excess(Address, Capacity); + +fn size_align() -> (usize, usize) { + (mem::size_of::(), mem::align_of::()) +} + +``` + +### Kind API +[kind api]: #kind-api + +```rust +/// Category for a memory record. +/// +/// An instance of `Kind` describes a particular layout of memory. +/// You build a `Kind` up as an input to give to an allocator. +/// +/// All kinds have an associated positive size; note that this implies +/// zero-sized types have no corresponding kind. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Kind { + // size of the requested block of memory, measured in bytes. + size: Size, + // alignment of the requested block of memory, measured in bytes. + // we ensure that this is always a power-of-two, because API's + ///like `posix_memalign` require it and it is a reasonable + // constraint to impose on Kind constructors. + // + // (However, we do not analogously require `align >= sizeof(void*)`, + // even though that is *also* a requirement of `posix_memalign`.) + align: Alignment, +} + + +// FIXME: audit default implementations for overflow errors, +// (potentially switching to overflowing_add and +// overflowing_mul as necessary). + +impl Kind { + // (private constructor) + fn from_size_align(size: usize, align: usize) -> Kind { + assert!(align.is_power_of_two()); + let size = unsafe { assert!(size > 0); NonZero::new(size) }; + let align = unsafe { assert!(align > 0); NonZero::new(align) }; + Kind { size: size, align: align } + } + + /// The minimum size in bytes for a memory block of this kind. + pub fn size(&self) -> NonZero { self.size } + + /// The minimum byte alignment for a memory block of this kind. + pub fn align(&self) -> NonZero { self.align } + + /// Constructs a `Kind` suitable for holding a value of type `T`. + /// Returns `None` if no such kind exists (e.g. for zero-sized `T`). + pub fn new() -> Option { + let (size, align) = size_align::(); + if size > 0 { Some(Kind::from_size_align(size, align)) } else { None } + } + + /// Produces kind describing a record that could be used to + /// allocate backing structure for `T` (which could be a trait + /// or other unsized type like a slice). + /// + /// Returns `None` when no such kind exists; for example, when `x` + /// is a reference to a zero-sized type. + pub fn for_value(t: &T) -> Option { + let (size, align) = (mem::size_of_val(t), mem::align_of_val(t)); + if size > 0 { + Some(Kind::from_size_align(size, align)) + } else { + None + } + } + + /// Creates a kind describing the record that can hold a value + /// of the same kind as `self`, but that also is aligned to + /// alignment `align` (measured in bytes). + /// + /// If `self` already meets the prescribed alignment, then returns + /// `self`. + /// + /// Note that this method does not add any padding to the overall + /// size, regardless of whether the returned kind has a different + /// alignment. In other words, if `K` has size 16, `K.align_to(32)` + /// will *still* have size 16. + pub fn align_to(&self, align: Alignment) -> Self { + if align > self.align { + let pow2_align = align.checked_next_power_of_two().unwrap(); + debug_assert!(pow2_align > 0); // (this follows from self.align > 0...) + Kind { align: unsafe { NonZero::new(pow2_align) }, + ..*self } + } else { + *self + } + } + + /// Returns the amount of padding we must insert after `self` + /// to ensure that the following address will satisfy `align` + /// (measured in bytes). + /// + /// Behavior undefined if `align` is not a power-of-two. + /// + /// Note that in practice, this is only useable if `align <= + /// self.align` otherwise, the amount of inserted padding would + /// need to depend on the particular starting address for the + /// whole record, because `self.align` would not provide + /// sufficient constraint. + 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); + return len_rounded_up - len; + } + + /// Creates a kind describing the record for `n` instances of + /// `self`, with a suitable amount of padding between each to + /// ensure that each instance is given its requested size and + /// alignment. On success, returns `(k, offs)` where `k` is the + /// kind of the array and `offs` is the distance between the start + /// of each element in the array. + /// + /// On zero `n` or arithmetic overflow, returns `None`. + pub fn repeat(&self, n: usize) -> Option<(Self, usize)> { + if n == 0 { return None; } + let padded_size = match self.size.checked_add(self.padding_needed_for(self.align)) { + None => return None, + Some(padded_size) => padded_size, + }; + let alloc_size = match padded_size.checked_mul(n) { + None => return None, + Some(alloc_size) => alloc_size, + }; + Some((Kind::from_size_align(alloc_size, *self.align), padded_size)) + } + + /// Creates a kind describing the record for `self` followed by + /// `next`, including any necessary padding to ensure that `next` + /// will be properly aligned. Note that the result kind will + /// satisfy the alignment properties of both `self` and `next`. + /// + /// Returns `Some((k, offset))`, where `k` is kind of the concatenated + /// record and `offset` is the relative location, in bytes, of the + /// start of the `next` embedded witnin the concatenated record + /// (assuming that the record itself starts at offset 0). + /// + /// On arithmetic overflow, returns `None`. + pub fn extend(&self, next: Self) -> Option<(Self, usize)> { + let new_align = unsafe { NonZero::new(cmp::max(*self.align, *next.align)) }; + let realigned = Kind { align: new_align, ..*self }; + let pad = realigned.padding_needed_for(new_align); + let offset = *self.size() + pad; + let new_size = offset + *next.size(); + Some((Kind::from_size_align(new_size, *new_align), offset)) + } + + /// Creates a kind describing the record for `n` instances of + /// `self`, with no padding between each instance. + /// + /// On zero `n` or overflow, returns `None`. + pub fn repeat_packed(&self, n: usize) -> Option { + let scaled = match self.size().checked_mul(n) { + None => return None, + Some(scaled) => scaled, + }; + let size = unsafe { assert!(scaled > 0); NonZero::new(scaled) }; + Some(Kind { size: size, align: self.align }) + } + + /// Creates a kind describing the record for `self` followed by + /// `next` with no additional padding between the two. Since no + /// padding is inserted, the alignment of `next` is irrelevant, + /// and is not incoporated *at all* into the resulting kind. + /// + /// Returns `(k, offset)`, where `k` is kind of the concatenated + /// record and `offset` is the relative location, in bytes, of the + /// start of the `next` embedded witnin the concatenated record + /// (assuming that the record itself starts at offset 0). + /// + /// (The `offset` is always the same as `self.size()`; we use this + /// signature out of convenience in matching the signature of + /// `fn extend`.) + /// + /// On arithmetic overflow, returns `None`. + pub fn extend_packed(&self, next: Self) -> Option<(Self, usize)> { + let new_size = match self.size().checked_add(*next.size()) { + None => return None, + Some(new_size) => new_size, + }; + let new_size = unsafe { NonZero::new(new_size) }; + Some((Kind { size: new_size, ..*self }, *self.size())) + } + + // Below family of methods *assume* inputs are pre- or + // post-validated in some manner. (The implementations here + ///do indirectly validate, but that is not part of their + /// specification.) + // + // Since invalid inputs could yield ill-formed kinds, these + // methods are `unsafe`. + + /// Creates kind describing the record for a single instance of `T`. + /// Requires `T` has non-zero size. + pub unsafe fn new_unchecked() -> Self { + let (size, align) = size_align::(); + Kind::from_size_align(size, align) + } + + + /// Creates a kind describing the record for `self` followed by + /// `next`, including any necessary padding to ensure that `next` + /// will be properly aligned. Note that the result kind will + /// satisfy the alignment properties of both `self` and `next`. + /// + /// Returns `(k, offset)`, where `k` is kind of the concatenated + /// record and `offset` is the relative location, in bytes, of the + /// start of the `next` embedded witnin the concatenated record + /// (assuming that the record itself starts at offset 0). + /// + /// Requires no arithmetic overflow from inputs. + pub unsafe fn extend_unchecked(&self, next: Self) -> (Self, usize) { + self.extend(next).unwrap() + } + + /// Creates a kind 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. + /// (See also the `fn array` checked variant.) + pub unsafe fn repeat_unchecked(&self, n: usize) -> (Self, usize) { + self.repeat(n).unwrap() + } + + /// Creates a kind describing the record for `n` instances of + /// `self`, with no padding between each instance. + /// + /// Requires non-zero `n` and no arithmetic overflow from inputs. + /// (See also the `fn array_packed` checked variant.) + pub unsafe fn repeat_packed_unchecked(&self, n: usize) -> Self { + self.repeat_packed(n).unwrap() + } + + /// Creates a kind describing the record for `self` followed by + /// `next` with no additional padding between the two. Since no + /// padding is inserted, the alignment of `next` is irrelevant, + /// and is not incoporated *at all* into the resulting kind. + /// + /// Returns `(k, offset)`, where `k` is kind of the concatenated + /// record and `offset` is the relative location, in bytes, of the + /// start of the `next` embedded witnin the concatenated record + /// (assuming that the record itself starts at offset 0). + /// + /// (The `offset` is always the same as `self.size()`; we use this + /// signature out of convenience in matching the signature of + /// `fn extend`.) + /// + /// Requires no arithmetic overflow from inputs. + /// (See also the `fn extend_packed` checked variant.) + pub unsafe fn extend_packed_unchecked(&self, next: Self) -> (Self, usize) { + self.extend_packed(next).unwrap() + } + + /// Creates a kind describing the record for a `[T; n]`. + /// + /// On zero `n`, zero-sized `T`, or arithmetic overflow, returns `None`. + pub fn array(n: usize) -> Option { + Kind::new::() + .and_then(|k| k.repeat(n)) + .map(|(k, offs)| { + debug_assert!(offs == mem::size_of::()); + k + }) + } + + /// Creates a kind describing the record for a `[T; n]`. + /// + /// Requires nonzero `n`, nonzero-sized `T`, and no arithmetic + /// overflow; otherwise behavior undefined. + pub fn array_unchecked(n: usize) -> Self { + Kind::array::(n).unwrap() + } + +} + +``` + +### AllocError API +[error api]: #allocerror-api + +```rust +/// `AllocError` instances provide feedback about the cause of an allocation failure. +pub trait AllocError { + /// Construct an error that indicates operation failure due to + /// invalid input values for the request. + /// + /// This can be used, for example, to signal an overflow occurred + /// during arithmetic computation. (However, since overflows + /// frequently represent an allocation attempt that would exhaust + /// memory, clients are alternatively allowed to constuct an error + /// representing memory exhaustion in such scenarios.) + fn invalid_input() -> Self; + + /// Returns true if the error is due to hitting some resource + /// limit or otherwise running out of memory. This condition + /// strongly implies that *some* series of deallocations would + /// allow a subsequent reissuing of the original allocation + /// request to succeed. + /// + /// Exhaustion is a common interpretation of an allocation failure; + /// e.g. usually when `malloc` returns `null`, it is because of + /// hitting a user resource limit or system memory exhaustion. + /// + /// Note that the resource exhaustion could be specific to the + /// original allocator (i.e. the only way to free up memory is by + /// deallocating memory attached to that allocator), or it could + /// be associated with some other state outside of the original + /// alloactor. The `AllocError` trait does not distinguish between + /// the two scenarios. + /// + /// Finally, error responses to allocation input requests that are + /// *always* illegal for *any* allocator (e.g. zero-sized or + /// arithmetic-overflowing requests) are allowed to respond `true` + /// here. (This is to allow `MemoryExhausted` as a valid error type + /// for an allocator that can handle all "sane" requests.) + fn is_memory_exhausted(&self) -> bool; + + /// Returns true if the allocator is fundamentally incapable of + /// satisfying the original request. This condition implies that + /// such an allocation request will never succeed on this + /// allocator, regardless of environment, memory pressure, or + /// other contextual condtions. + /// + /// An example where this might arise: A block allocator that only + /// supports satisfying memory requests where each allocated block + /// is at most `K` bytes in size. + fn is_request_unsupported(&self) -> bool; + + /// Returns true only if the error is transient. "Transient" is + /// meant here in the sense that there is a reasonable chance that + /// re-issuing the same allocation request in the future *could* + /// succeed, even if nothing else changes about the overall + /// context of the request. + /// + /// An example where this might arise: An allocator shared across + /// threads that fails upon detecting interference (rather than + /// e.g. blocking). + fn is_transient(&self) -> bool { false } // most errors are not transient +} + +/// The `MemoryExhausted` error represents a blanket condition +/// that the given request was not satisifed for some reason beyond +/// any particular limitations of a given allocator. +/// +/// It roughly corresponds to getting `null` back from a call to `malloc`: +/// you've probably exhausted memory (though there might be some other +/// explanation; see discussion with `AllocError::is_memory_exhausted`). +/// +/// Allocators that can in principle allocate any kind of legal input +/// might choose this as their associated error type. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct MemoryExhausted; + +/// The `AllocErr` error specifies whether an allocation failure is +/// specifically due to resource exhaustion or if it is due to +/// something wrong when combining the given input arguments with this +/// allocator. + +/// Allocators that only support certain classes of inputs might choose this +/// as their associated error type, so that clients can respond appropriately +/// to specific error failure scenarios. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum AllocErr { + /// Error due to hitting some resource limit or otherwise running + /// out of memory. This condition strongly implies that *some* + /// series of deallocations would allow a subsequent reissuing of + /// the original allocation request to succeed. + Exhausted, + + /// Error due to allocator being fundamentally incapable of + /// satisfying the original request. This condition implies that + /// such an allocation request will never succeed on the given + /// allocator, regardless of environment, memory pressure, or + /// other contextual condtions. + Unsupported, +} + +impl AllocError for MemoryExhausted { + fn invalid_input() -> Self { MemoryExhausted } + fn is_memory_exhausted(&self) -> bool { true } + fn is_request_unsupported(&self) -> bool { false } +} + +impl AllocError for AllocErr { + fn invalid_input() -> Self { AllocErr::Unsupported } + fn is_memory_exhausted(&self) -> bool { *self == AllocErr::Exhausted } + fn is_request_unsupported(&self) -> bool { *self == AllocErr::Unsupported } +} + +``` + +### Allocator trait header +[trait header]: #allocator-trait-header + +```rust +/// An implementation of `Allocator` can allocate, reallocate, and +/// deallocate arbitrary blocks of data described via `Kind`. +/// +/// Some of the methods require that a kind *fit* a memory block. +/// What it means for a kind to "fit" a memory block means is that +/// the following two conditions must hold: +/// +/// 1. The block's starting address must be aligned to `kind.align()`. +/// +/// 2. The block's size must fall in the range `[orig, usable]`, where: +/// +/// * `orig` is the size last used to allocate the block, and +/// +/// * `usable` is the capacity that was (or would have been) +/// returned when (if) the block was allocated via a call to +/// `alloc_excess` or `realloc_excess`. +/// +/// Note that due to the constraints in the methods below, a +/// lower-bound on `usable` can be safely approximated by a call to +/// `usable_size`. +pub unsafe trait Allocator { + /// When allocation requests cannot be satisified, an instance of + /// this error is returned. + /// + /// Many allocators will want to use the zero-sized + /// `MemoryExhausted` type for this. + type Error: AllocError + fmt::Debug; + +``` + +### Allocator core alloc and dealloc +[alloc and dealloc]: #allocator-core-alloc-and-dealloc + +```rust + /// Returns a pointer suitable for holding data described by + /// `kind`, meeting its size and alignment guarantees. + /// + /// The returned block of storage may or may not have its contents + /// initialized. (Extension subtraits might restrict this + /// behavior, e.g. to ensure initialization.) + /// + /// Returns `Err` if allocation fails or if `kind` does + /// not meet allocator's size or alignment constraints. + unsafe fn alloc(&mut self, kind: Kind) -> Result; + + /// Deallocate the memory referenced by `ptr`. + /// + /// `ptr` must have previously been provided via this allocator, + /// and `kind` must *fit* the provided block (see above); + /// otherwise yields undefined behavior. + /// + /// Returns `Err` only if deallocation fails in some fashion. If + /// the returned error is *transient*, then ownership of the + /// memory block is transferred back to the caller (see + /// `AllocError::is_transient`). Otherwise, callers must assume + /// that ownership of the block has been unrecoverably lost. + /// + /// Note: Implementors are encouraged to avoid `Err`-failure from + /// `dealloc`; most memory allocation APIs do not support + /// signalling failure in their `free` routines, and clients are + /// likely to incorporate that assumption into their own code and + /// just `unwrap` the result of this call. + unsafe fn dealloc(&mut self, ptr: Address, kind: Kind) -> Result<(), Self::Error>; + + /// Allocator-specific method for signalling an out-of-memory + /// condition. + /// + /// Any activity done by the `oom` method should ensure that it + /// does not infinitely regress in nested calls to `oom`. In + /// practice this means implementors should eschew allocating, + /// especially from `self` (directly or indirectly). + /// + /// Implementors of this trait are discouraged from panicking or + /// aborting from other methods in the event of memory exhaustion; + /// instead they should return an appropriate error from the + /// invoked method, and let the client decide whether to invoke + /// this `oom` method. + unsafe fn oom(&mut self) -> ! { ::core::intrinsics::abort() } + +``` + +### Allocator-specific quantities and limits +[quantites and limits]: #allocator-specific-quantities-and-limits + +```rust + // == ALLOCATOR-SPECIFIC QUANTITIES AND LIMITS == + // max_size, max_align, usable_size + + /// The maximum requestable size in bytes for memory blocks + /// managed by this allocator. + /// + /// Returns `None` if this allocator has no explicit maximum size. + /// (Note that such allocators may well still have an *implicit* + /// maximum size; i.e. allocation requests can always fail.) + fn max_size(&self) -> Option { None } + + /// The maximum requestable alignment in bytes for memory blocks + /// managed by this allocator. + /// + /// Returns `None` if this allocator has no assigned maximum + /// alignment. (Note that such allocators may well still have an + /// *implicit* maximum alignment; i.e. allocation requests can + /// always fail.) + fn max_align(&self) -> Option { None } + + /// Returns the minimum guaranteed usable size of a successful + /// allocation created with the specified `kind`. + /// + /// Clients who wish to make use of excess capacity are encouraged + /// to use the `alloc_excess` and `realloc_excess` instead, as + /// this method is constrained to conservatively report a value + /// less than or equal to the minimum capacity for *all possible* + /// calls to those methods. + /// + /// However, for clients that do not wish to track the capacity + /// returned by `alloc_excess` locally, this method is likely to + /// produce useful results. + unsafe fn usable_size(&self, kind: Kind) -> Capacity { kind.size() } + +``` + +### Allocator methods for memory reuse +[memory reuse]: #allocator-methods-for-memory-reuse + +```rust + // == METHODS FOR MEMORY REUSE == + // realloc. alloc_excess, realloc_excess + + /// Returns a pointer suitable for holding data described by + /// `new_kind`, meeting its size and alignment guarantees. To + /// accomplish this, this may extend or shrink the allocation + /// referenced by `ptr` to fit `new_kind`. + /// + /// * `ptr` must have previously been provided via this allocator. + /// + /// * `kind` must *fit* the `ptr` (see above). (The `new_kind` + /// argument need not fit it.) + /// + /// Behavior undefined if either of latter two constraints are unmet. + /// + /// In addition, `new_kind` should not impose a stronger alignment + /// constraint than `kind`. (In other words, `new_kind.align()` + /// must evenly divide `kind.align()`; note this implies the + /// alignment of `new_kind` must not exceed that of `kind`.) + /// However, behavior is well-defined (though underspecified) when + /// this constraint is violated; further discussion below. + /// + /// If this returns `Ok`, then ownership of the memory block + /// referenced by `ptr` has been transferred to this + /// allocator. The memory may or may not have been freed, and + /// should be considered unusable (unless of course it was + /// transferred back to the caller again via the return value of + /// this method). + /// + /// Returns `Err` only if `new_kind` does not meet the allocator's + /// size and alignment constraints of the allocator or the + /// alignment of `kind`, or if reallocation otherwise fails. (Note + /// that did not say "if and only if" -- in particular, an + /// implementation of this method *can* return `Ok` if + /// `new_kind.align() > old_kind.align()`; or it can return `Err` + /// in that scenario.) + /// + /// If this method returns `Err`, then ownership of the memory + /// block has not been transferred to this allocator, and the + /// contents of the memory block are unaltered. + unsafe fn realloc(&mut self, + ptr: Address, + kind: Kind, + new_kind: Kind) -> Result { + // All Kind alignments are powers of two, so a comparison + // suffices here (rather than resorting to a `%` operation). + if new_kind.size() <= self.usable_size(kind) && new_kind.align() <= kind.align() { + return Ok(ptr); + } else { + let result = self.alloc(new_kind); + if let Ok(new_ptr) = result { + ptr::copy(*ptr as *const u8, *new_ptr, cmp::min(*kind.size(), *new_kind.size())); + loop { + if let Err(err) = self.dealloc(ptr, kind) { + // all we can do from the realloc abstraction + // is either: + // + // 1. free the block we just finished copying + // into and pass the error up, + // 2. ignore the dealloc error, or + // 3. try again. + // + // They are all terrible; 1 seems unjustifiable. + // So we choose 2, unless the error is transient. + if err.is_transient() { continue; } + } + break; + } + } + result + } + } + + /// Behaves like `fn alloc`, but also returns the whole size of + /// the returned block. For some `kind` inputs, like arrays, this + /// may include extra storage usable for additional data. + unsafe fn alloc_excess(&mut self, kind: Kind) -> Result { + self.alloc(kind).map(|p| Excess(p, self.usable_size(kind))) + } + + /// Behaves like `fn realloc`, but also returns the whole size of + /// the returned block. For some `kind` inputs, like arrays, this + /// may include extra storage usable for additional data. + unsafe fn realloc_excess(&mut self, + ptr: Address, + kind: Kind, + new_kind: Kind) -> Result { + self.realloc(ptr, kind, new_kind) + .map(|p| Excess(p, self.usable_size(new_kind))) + } + +``` + +### Allocator common usage patterns +[common usage patterns]: #allocator-common-usage-patterns + +```rust + // == COMMON USAGE PATTERNS == + // alloc_one, dealloc_one, alloc_array, realloc_array. dealloc_array + + /// Allocates a block suitable for holding an instance of `T`. + /// + /// Captures a common usage pattern for allocators. + /// + /// The returned block is suitable for passing to the + /// `alloc`/`realloc` methods of this allocator. + unsafe fn alloc_one(&mut self) -> Result, Self::Error> { + if let Some(k) = Kind::new::() { + self.alloc(k).map(|p|Unique::new(*p as *mut T)) + } else { + // (only occurs for zero-sized T) + debug_assert!(mem::size_of::() == 0); + Err(Self::Error::invalid_input()) + } + } + + /// Deallocates a block suitable for holding an instance of `T`. + /// + /// Captures a common usage pattern for allocators. + unsafe fn dealloc_one(&mut self, mut ptr: Unique) -> Result<(), Self::Error> { + let raw_ptr = NonZero::new(ptr.get_mut() as *mut T as *mut u8); + self.dealloc(raw_ptr, Kind::new::().unwrap()) + } + + /// Allocates a block suitable for holding `n` instances of `T`. + /// + /// Captures a common usage pattern for allocators. + /// + /// The returned block is suitable for passing to the + /// `alloc`/`realloc` methods of this allocator. + unsafe fn alloc_array(&mut self, n: usize) -> Result, Self::Error> { + match Kind::array::(n) { + Some(kind) => self.alloc(kind).map(|p|Unique::new(*p as *mut T)), + None => Err(Self::Error::invalid_input()), + } + } + + /// Reallocates a block previously suitable for holding `n_old` + /// instances of `T`, returning a block suitable for holding + /// `n_new` instances of `T`. + /// + /// Captures a common usage pattern for allocators. + /// + /// The returned block is suitable for passing to the + /// `alloc`/`realloc` methods of this allocator. + unsafe fn realloc_array(&mut self, + ptr: Unique, + n_old: usize, + n_new: usize) -> Result, Self::Error> { + let old_new_ptr = (Kind::array::(n_old), Kind::array::(n_new), *ptr); + if let (Some(k_old), Some(k_new), ptr) = old_new_ptr { + self.realloc(NonZero::new(ptr as *mut u8), k_old, k_new) + .map(|p|Unique::new(*p as *mut T)) + } else { + Err(Self::Error::invalid_input()) + } + } + + /// Deallocates a block suitable for holding `n` instances of `T`. + /// + /// Captures a common usage pattern for allocators. + unsafe fn dealloc_array(&mut self, ptr: Unique, n: usize) -> Result<(), Self::Error> { + let raw_ptr = NonZero::new(*ptr as *mut u8); + if let Some(k) = Kind::array::(n) { + self.dealloc(raw_ptr, k) + } else { + Err(Self::Error::invalid_input()) + } + } + +``` + +### Allocator unchecked method variants +[unchecked variants]: #allocator-unchecked-method-variants + +```rust + // UNCHECKED METHOD VARIANTS + + /// Returns a pointer suitable for holding data described by + /// `kind`, meeting its size and alignment guarantees. + /// + /// The returned block of storage may or may not have its contents + /// initialized. (Extension subtraits might restrict this + /// behavior, e.g. to ensure initialization.) + /// + /// Returns `None` if request unsatisfied. + /// + /// Behavior undefined if input does not meet size or alignment + /// constraints of this allocator. + unsafe fn alloc_unchecked(&mut self, kind: Kind) -> Option
{ + // (default implementation carries checks, but impl's are free to omit them.) + self.alloc(kind).ok() + } + + /// Deallocate the memory referenced by `ptr`. + /// + /// `ptr` must have previously been provided via this allocator, + /// and `kind` must *fit* the provided block (see above). + /// Otherwise yields undefined behavior. + unsafe fn dealloc_unchecked(&mut self, ptr: Address, kind: Kind) { + // (default implementation carries checks, but impl's are free to omit them.) + self.dealloc(ptr, kind).unwrap() + } + + /// Returns a pointer suitable for holding data described by + /// `new_kind`, meeting its size and alignment guarantees. To + /// accomplish this, may extend or shrink the allocation + /// referenced by `ptr` to fit `new_kind`. + //// + /// (In other words, ownership of the memory block associated with + /// `ptr` is first transferred back to this allocator, but the + /// same block may or may not be transferred back as the result of + /// this call.) + /// + /// * `ptr` must have previously been provided via this allocator. + /// + /// * `kind` must *fit* the `ptr` (see above). (The `new_kind` + /// argument need not fit it.) + /// + /// * `new_kind` must meet the allocator's size and alignment + /// constraints. In addition, `new_kind.align()` must equal + /// `kind.align()`. (Note that this is a stronger constraint + /// that that imposed by `fn realloc`.) + /// + /// Behavior undefined if any of latter three constraints are unmet. + /// + /// If this returns `Some`, then the memory block referenced by + /// `ptr` may have been freed and should be considered unusable. + /// + /// Returns `None` if reallocation fails; in this scenario, the + /// original memory block referenced by `ptr` is unaltered. + unsafe fn realloc_unchecked(&mut self, + ptr: Address, + kind: Kind, + new_kind: Kind) -> Option
{ + // (default implementation carries checks, but impl's are free to omit them.) + self.realloc(ptr, kind, new_kind).ok() + } + + /// Behaves like `fn alloc_unchecked`, but also returns the whole + /// size of the returned block. + unsafe fn alloc_excess_unchecked(&mut self, kind: Kind) -> Option { + self.alloc_excess(kind).ok() + } + + /// Behaves like `fn realloc_unchecked`, but also returns the + /// whole size of the returned block. + unsafe fn realloc_excess_unchecked(&mut self, + ptr: Address, + kind: Kind, + new_kind: Kind) -> Option { + self.realloc_excess(ptr, kind, new_kind).ok() + } + + + /// Allocates a block suitable for holding `n` instances of `T`. + /// + /// Captures a common usage pattern for allocators. + /// + /// Requires inputs are non-zero and do not cause arithmetic + /// overflow, and `T` is not zero sized; otherwise yields + /// undefined behavior. + unsafe fn alloc_array_unchecked(&mut self, n: usize) -> Option> { + let kind = Kind::array_unchecked::(n); + self.alloc_unchecked(kind).map(|p|Unique::new(*p as *mut T)) + } + + /// Reallocates a block suitable for holding `n_old` instances of `T`, + /// returning a block suitable for holding `n_new` instances of `T`. + /// + /// Captures a common usage pattern for allocators. + /// + /// Requires inputs are non-zero and do not cause arithmetic + /// overflow, and `T` is not zero sized; otherwise yields + /// undefined behavior. + unsafe fn realloc_array_unchecked(&mut self, + ptr: Unique, + n_old: usize, + n_new: usize) -> Option> { + let (k_old, k_new, ptr) = (Kind::array_unchecked::(n_old), + Kind::array_unchecked::(n_new), + *ptr); + self.realloc_unchecked(NonZero::new(ptr as *mut u8), k_old, k_new) + .map(|p|Unique::new(*p as *mut T)) + } + + /// Deallocates a block suitable for holding `n` instances of `T`. + /// + /// Captures a common usage pattern for allocators. + /// + /// Requires inputs are non-zero and do not cause arithmetic + /// overflow, and `T` is not zero sized; otherwise yields + /// undefined behavior. + unsafe fn dealloc_array_unchecked(&mut self, ptr: Unique, n: usize) { + let kind = Kind::array_unchecked::(n); + self.dealloc_unchecked(NonZero::new(*ptr as *mut u8), kind); + } +} +``` From 738ebe304ee4f7201f74eb01203871ea347f1530 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Sun, 6 Dec 2015 19:41:52 +0100 Subject: [PATCH 02/37] oops this question was folded into the previous one. --- text/0000-kinds-of-allocators.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 4cbff5c22a3..67c844d04c3 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -709,9 +709,6 @@ to both the allocation and deallocation call sites. callers who can make use of excess memory to avoid unnecessary calls to `realloc`. - -### Why `alloc_array_unchecked` and `dealloc_array_unchecked`? - ### Why the `Kind` abstraction? While we do want to require clients to hand the allocator the size and From af6090fbb297488cd7fff8763a1f6df62a391181 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Sun, 6 Dec 2015 19:43:49 +0100 Subject: [PATCH 03/37] oops `RequestUnsatisfied` was removed during the drafting process... --- text/0000-kinds-of-allocators.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 67c844d04c3..9732a1d2592 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -729,13 +729,8 @@ overhead (at least in terms of the size of the values being returned from the allocation calls), because the error type for the `Result` can be zero-sized if so desired. That is why the error is an associated type of the `Allocator`: allocators that want to ensure the -results have minimum size can use the zero-sized `RequestUnsatisfied` -or `MemoryExhausted` types as their associated `Self::Error`. - - * `RequestUnsatisfied` is a catch-all type that any allocator - could use as its error type; doing so provides no hint to the - client as to what they could do to try to service future memory - requests. +results have minimum size can use the zero-sized `MemoryExhausted` type +as their associated `Self::Error`. * `MemoryExhausted` is a specific error type meant for allocators that could in principle handle *any* sane input request, if there From be627c252ad33f314bfd74251009d46ac9d971b0 Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Sun, 6 Dec 2015 19:46:35 -0500 Subject: [PATCH 04/37] typo fix --- text/0000-kinds-of-allocators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 9732a1d2592..79be93312eb 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -91,7 +91,7 @@ how to integrate allocators with GC.) ## The `Allocator` trait at a glance -The source code for the `Allocator` trait prototype ks provided in an +The source code for the `Allocator` trait prototype is provided in an [appendix][Source for Allocator]. But since that section is long, here we summarize the high-level points of the `Allocator` API. From e76929e6434768121919cfffe40953e565339745 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Dec 2015 17:16:01 +0100 Subject: [PATCH 05/37] Fix realloc bug in spec and impl. --- text/0000-kinds-of-allocators.md | 43 ++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 79be93312eb..c4a093b75a2 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1636,17 +1636,22 @@ impl AllocError for AllocErr { /// /// 1. The block's starting address must be aligned to `kind.align()`. /// -/// 2. The block's size must fall in the range `[orig, usable]`, where: +/// 2. The block's size must fall in the range `[use_min, use_max]`, where: /// -/// * `orig` is the size last used to allocate the block, and +/// * `use_min` is `self.usable_size(kind).0`, and /// -/// * `usable` is the capacity that was (or would have been) +/// * `use_max` is the capacity that was (or would have been) /// returned when (if) the block was allocated via a call to /// `alloc_excess` or `realloc_excess`. /// -/// Note that due to the constraints in the methods below, a -/// lower-bound on `usable` can be safely approximated by a call to -/// `usable_size`. +/// Note that: +/// +/// * the size of the kind most recently used to allocate the block +/// is guaranteed to be in the range `[use_min, use_max]`, and +/// +/// * a lower-bound on `use_max` can be safely approximated by a call to +/// `usable_size`. +/// pub unsafe trait Allocator { /// When allocation requests cannot be satisified, an instance of /// this error is returned. @@ -1732,9 +1737,21 @@ pub unsafe trait Allocator { /// always fail.) fn max_align(&self) -> Option { None } - /// Returns the minimum guaranteed usable size of a successful + /// Returns bounds on the guaranteed usable size of a successful /// allocation created with the specified `kind`. /// + /// In particular, for a given kind `k`, if `usable_size(k)` returns + /// `(l, m)`, then one can use a block of kind `k` as if it has any + /// size in the range `[l, m]` (inclusive). + /// + /// (All implementors of `fn usable_size` must ensure that + /// `l <= k.size() <= m`) + /// + /// Both the lower- and upper-bounds (`l` and `m` respectively) are + /// provided: An allocator based on size classes could misbehave + /// if one attempts to deallocate a block without providing a + /// correct value for its size (i.e., one within the range `[l, m]`). + /// /// Clients who wish to make use of excess capacity are encouraged /// to use the `alloc_excess` and `realloc_excess` instead, as /// this method is constrained to conservatively report a value @@ -1744,7 +1761,9 @@ pub unsafe trait Allocator { /// However, for clients that do not wish to track the capacity /// returned by `alloc_excess` locally, this method is likely to /// produce useful results. - unsafe fn usable_size(&self, kind: Kind) -> Capacity { kind.size() } + unsafe fn usable_size(&self, kind: Kind) -> (Capacity, Capacity) { + (kind.size(), kind.size()) + } ``` @@ -1796,9 +1815,11 @@ pub unsafe trait Allocator { ptr: Address, kind: Kind, new_kind: Kind) -> Result { + let (min, max) = self.usable_size(kind); + let s = new_kind.size(); // All Kind alignments are powers of two, so a comparison // suffices here (rather than resorting to a `%` operation). - if new_kind.size() <= self.usable_size(kind) && new_kind.align() <= kind.align() { + if min <= s && s <= max && new_kind.align() <= kind.align() { return Ok(ptr); } else { let result = self.alloc(new_kind); @@ -1829,7 +1850,7 @@ pub unsafe trait Allocator { /// the returned block. For some `kind` inputs, like arrays, this /// may include extra storage usable for additional data. unsafe fn alloc_excess(&mut self, kind: Kind) -> Result { - self.alloc(kind).map(|p| Excess(p, self.usable_size(kind))) + self.alloc(kind).map(|p| Excess(p, self.usable_size(kind).1)) } /// Behaves like `fn realloc`, but also returns the whole size of @@ -1840,7 +1861,7 @@ pub unsafe trait Allocator { kind: Kind, new_kind: Kind) -> Result { self.realloc(ptr, kind, new_kind) - .map(|p| Excess(p, self.usable_size(new_kind))) + .map(|p| Excess(p, self.usable_size(new_kind).1)) } ``` From 087f4c136b44536d76ac748d132658941c552278 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Dec 2015 17:35:09 +0100 Subject: [PATCH 06/37] removed transient errors from API. --- text/0000-kinds-of-allocators.md | 86 ++++++++++++++------------------ 1 file changed, 37 insertions(+), 49 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index c4a093b75a2..288fcc29e5b 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -292,17 +292,23 @@ will expose: 3. there could be *interference* between two threads. This latter scenario means that this allocator failed on this memory request, but the client might - quite reasonably just *retry* the request. + quite reasonably just *retry* the request. This is + an error condition specific to this allocator, so we + will identify it via a separate `fn is_transient` inherent + method. ```rust #[derive(Copy, Clone, PartialEq, Eq, Debug)] enum BumpAllocError { Invalid, MemoryExhausted, Interference } +impl BumpAllocError { + fn is_transient(&self) { *self == BumpAllocError::Interference } +} + impl alloc::AllocError for BumpAllocError { fn invalid_input() -> Self { BumpAllocError::MemoryExhausted } fn is_memory_exhausted(&self) -> bool { *self == BumpAllocError::MemoryExhausted } fn is_request_unsupported(&self) -> bool { false } - fn is_transient(&self) { *self == BumpAllocError::Interference } } ``` @@ -989,8 +995,9 @@ few motivating examples that *are* clearly feasible and useful. * Should `dealloc` return a `Result` or not? (Under what circumstances would we expect `dealloc` to fail in a manner worth signalling? The main one I can think of is a transient failure, - which is why the documentation for that method spends so much time - discussing it.) + which was in a previous version of the API but has since been removed. + Still, if errors *can* happen, maybe its best to provide *some* way + for a client to catch them and report them in context.) * Are the type definitions for `Size`, `Capacity`, `Alignment`, and `Address` an abuse of the `NonZero` type? (Or do we just need some @@ -1000,25 +1007,13 @@ few motivating examples that *are* clearly feasible and useful. client to provide more contextual information about the OOM condition)? - * Does `AllocError::is_transient` belong in this version of the API, - or should we wait to add it later? (I originally suspected that - libstd data types would want to make use of it, which would means - we should add it. However, in the absence of a concrete example - stdlib type that would use it, we may be better off removing `fn - is_transient` from this API (instead specifying that allocators - with such transient failures will block (i.e. loop and retry - internally), with the expectation that if a need for such an - allocator does arise, we will then represent the API extension - via a different trait (perhaps an extension trait of `Allocator`). - - * On that note, if we remove the `fn is_transient` method, should - we get rid of the `AllocError` bound entirely? Is the given set + * Should we get rid of the `AllocError` bound entirely? Is the given set of methods actually worth providing to all generic clients? (Keeping it seems very low cost to me; implementors can always opt to use the `MemoryExhausted` error type, which is cheap. But my intuition may be wrong.) - + * Do we need `Allocator::max_size` and `Allocator::max_align` ? * Should default impl of `Allocator::max_align` return `None`, or is @@ -1033,6 +1028,12 @@ few motivating examples that *are* clearly feasible and useful. low-level allocators will behave well for large alignments. See https://github.com/rust-lang/rust/issues/30170 +# Change History + +* Changed `fn usable_size` to return `(l, m)` rathern than just `m`. + +* Removed `fn is_transient` from `trait AllocError`, and removed discussion + of transient errors from the API. # Appendices @@ -1559,17 +1560,6 @@ pub trait AllocError { /// supports satisfying memory requests where each allocated block /// is at most `K` bytes in size. fn is_request_unsupported(&self) -> bool; - - /// Returns true only if the error is transient. "Transient" is - /// meant here in the sense that there is a reasonable chance that - /// re-issuing the same allocation request in the future *could* - /// succeed, even if nothing else changes about the overall - /// context of the request. - /// - /// An example where this might arise: An allocator shared across - /// threads that fails upon detecting interference (rather than - /// e.g. blocking). - fn is_transient(&self) -> bool { false } // most errors are not transient } /// The `MemoryExhausted` error represents a blanket condition @@ -1683,11 +1673,9 @@ pub unsafe trait Allocator { /// and `kind` must *fit* the provided block (see above); /// otherwise yields undefined behavior. /// - /// Returns `Err` only if deallocation fails in some fashion. If - /// the returned error is *transient*, then ownership of the - /// memory block is transferred back to the caller (see - /// `AllocError::is_transient`). Otherwise, callers must assume - /// that ownership of the block has been unrecoverably lost. + /// Returns `Err` only if deallocation fails in some fashion. + /// In this case callers must assume that ownership of the block has + /// been unrecoverably lost (memory may have been leaked). /// /// Note: Implementors are encouraged to avoid `Err`-failure from /// `dealloc`; most memory allocation APIs do not support @@ -1825,21 +1813,21 @@ pub unsafe trait Allocator { let result = self.alloc(new_kind); if let Ok(new_ptr) = result { ptr::copy(*ptr as *const u8, *new_ptr, cmp::min(*kind.size(), *new_kind.size())); - loop { - if let Err(err) = self.dealloc(ptr, kind) { - // all we can do from the realloc abstraction - // is either: - // - // 1. free the block we just finished copying - // into and pass the error up, - // 2. ignore the dealloc error, or - // 3. try again. - // - // They are all terrible; 1 seems unjustifiable. - // So we choose 2, unless the error is transient. - if err.is_transient() { continue; } - } - break; + if let Err(_) = self.dealloc(ptr, kind) { + // all we can do from the realloc abstraction + // is either: + // + // 1. free the block we just finished copying + // into and pass the error up, + // 2. panic (same as if we had called `unwrap`), + // 3. try to dealloc again, or + // 4. ignore the dealloc error. + // + // They are all terrible; (1.) and (2.) seem unjustifiable, + // and (3.) seems likely to yield an infinite loop (unless + // we add back in some notion of a transient error + // into the API). + // So we choose (4.): ignore the dealloc error. } } result From af0b05f3dffcad2b9579efc93a7c368a4a4ecaf7 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Dec 2015 17:42:27 +0100 Subject: [PATCH 07/37] try to improve description of `fn is_memory_exhausted` --- text/0000-kinds-of-allocators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 288fcc29e5b..6aaace1d2b0 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1528,7 +1528,7 @@ pub trait AllocError { /// Returns true if the error is due to hitting some resource /// limit or otherwise running out of memory. This condition - /// strongly implies that *some* series of deallocations would + /// serves as a hint that some series of deallocations *might* /// allow a subsequent reissuing of the original allocation /// request to succeed. /// From 533bcf8bc52133cd01d0cf3ca0c917b1fe7d1da3 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Dec 2015 17:59:56 +0100 Subject: [PATCH 08/37] small updates: IEtF 2119, and discussion of `&mut MegaEmbedded` --- text/0000-kinds-of-allocators.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 6aaace1d2b0..c84e375772d 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -493,7 +493,11 @@ blocks and the allocator(s), but the generic code we expect the standard library to provide cannot make such assumptions. To satisfy the above scenarios in a sane, consistent, general fashion, -the `Allocator` trait assumes/requires all of the following: +the `Allocator` trait assumes/requires all of the following conditions. +(Note: this list of conditions uses the phrases "should", "must", and "must not" +in a formal manner, in the style of [IETF RFC 2119][].) + +[IETF RFC 2119]: https://www.ietf.org/rfc/rfc2119.txta 1. (for allocator impls and clients): in the absence of other information (e.g. specific allocator implementations), all blocks @@ -550,15 +554,25 @@ the `Allocator` trait assumes/requires all of the following: E.g. this is *not* a legal allocator: ```rust struct MegaEmbedded { pool: [u8; 1024*1024], cursor: usize, ... } - impl Allocator for MegaEmbedded { ... } + impl Allocator for MegaEmbedded { ... } // INVALID IMPL ``` The latter impl is simply unreasonable (at least if one is intending to satisfy requests by returning pointers into `self.bytes`). - (Note of course, `impl Allocator for &mut MegaEmbedded` is in - principle *fine*; that would then be an allocator that is an - indirect handle to an unembedded pool.) + Note that an allocator that owns its pool *indirectly* + (i.e. does not have the pool's state embedded in the allocator) is fine: + ```rust + struct MegaIndirect { pool: *mut [u8; 1024*1024], cursor: usize, ... } + impl Allocator for MegaIndirect { ... } // OKAY + ``` + + (I originally claimed that `impl Allocator for &mut MegaEmbedded` + would also be a legal example of an allocator that is an indirect handle + to an unembedded pool, but others pointed out that handing out the + addresses pointing into that embedded pool could end up violating our + aliasing rules for `&mut`. I obviously did not expect that outcome; I + would be curious to see what the actual design space is here.) 5. (for allocator impls and clients) if an allocator is cloneable, the client *can assume* that all clones From 1fc45cd40a268ae6b534e56000ef7dc9eb729bfe Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Dec 2015 18:05:35 +0100 Subject: [PATCH 09/37] account for the RefCell oversight. --- text/0000-kinds-of-allocators.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index c84e375772d..9c183e3633a 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -194,6 +194,14 @@ case, dropping the allocator has no effect on the memory pool. handle will not drop the pool as long as at least one other handle remains, but dropping the last handle will drop the pool itself. + FIXME: `RefCell` is not going to work with the allocator API + envisaged here; see [comment from gankro][]. We will need to + address this (perhaps just by pointing out that it is illegal and + suggesting a standard pattern to work around it) before this RFC + can be accepted. + +[comment from gankro]: https://github.com/rust-lang/rfcs/pull/1398#issuecomment-162681096 + A client that is generic over all possible `A:Allocator` instances cannot know which of the above cases it falls in. This has consequences in terms of the restrictions that must be met by client code @@ -1001,6 +1009,9 @@ few motivating examples that *are* clearly feasible and useful. # Unresolved questions [unresolved]: #unresolved-questions + * Since we cannot do `RefCell` (see FIXME above), what is + our standard recommendation for what to do instead? + * Should `Kind` be an associated type of `Allocator` (see [alternatives][] section for discussion). (In fact, most of the "Variations correspond to potentially From 553d59ed672038ab1a2964fcb978fbfb4b3f2e8b Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Dec 2015 18:08:19 +0100 Subject: [PATCH 10/37] fix typo. --- text/0000-kinds-of-allocators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 9c183e3633a..01aca4a5cf0 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -860,7 +860,7 @@ ensuring e.g. that allocated blocks of memory can be scanned (i.e. "parsed") by the GC (if that in fact ends up being necessary). This way, we can deploy an `Allocator` trait API today that does not -provide the necessary reflective hooks that a GC wuold need to access. +provide the necessary reflective hooks that a GC would need to access. Crates that define their own `Allocator` implementations without also claiming them to be GC-compatible will be forbidden from linking with From d9a9f2d924cc65c2985464717206ea4f5979c7bc Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 12:58:58 +0100 Subject: [PATCH 11/37] added discussion of std lib extension. --- text/0000-kinds-of-allocators.md | 73 ++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 01aca4a5cf0..417f5988779 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -460,6 +460,79 @@ fn main() { And that's all to the demo, folks. +### What about standard library containers? + +The intention of this RFC is that the Rust standard library will be +extended with parameteric allocator support: `Vec`, `HashMap`, etc +should all eventually be extended with the ability to use an +alternative allocator for their backing storage. + +However, this RFC does not prescribe when or how this should happen. + +Under the design of this RFC, Allocators parameters are specified via +a *generic type parameter* on the container type. This strongly +implies that `Vec` and `HashMap` will need to be extended +with an allocator type parameter, i.e.: `Vec` and +`HashMap`. + +There are two reasons why such extension is left to later work, after +this RFC. + +#### Default type parameter fallback + +On its own, such a change would be backwards incompatible (i.e. a huge +breaking change), and also would simply be just plain inconvenient for +typical use cases. Therefore, the newly added type parameters will +almost certainly require a *default type*: `Vec` and +`HashMap`. + +Default type parameters themselves, in the context of type defintions, +are a stable part of the Rust language. + +However, the exact semantics of how default type parameters interact +with inference is still being worked out (in part *because* allocators +are a motivating use case), as one can see by reading the following: + +* RFC 213, "Finalize defaulted type parameters": https://github.com/rust-lang/rfcs/blob/master/text/0213-defaulted-type-params.md + + * Tracking Issue for RFC 213: Default Type Parameter Fallback: https://github.com/rust-lang/rust/issues/27336 + +* Feature gate defaulted type parameters appearing outside of types: https://github.com/rust-lang/rust/pull/30724 + +#### Fully general container integration needs Dropck Eyepatch + +The previous problem was largely one of programmer +ergonomics. However, there is also a subtle soundness issue that +arises due to an current implementation artifact. + +Standard library types like `Vec` and `HashMap` allow +instantiating the generic parameters `T`, `K`, `V` with types holding +lifetimes that do not strictly outlive that of the container itself. +(I will refer to such instantiations of `Vec` and `HashMap` +"same-lifetime instances" as a shorthand in this discussion.) + +Same-lifetime instance support is currently implemented for `Vec` and +`HashMap` via an unstable attribute that is too +coarse-grained. Therefore, we cannot soundly add the allocator +parameter to `Vec` and `HashMap` while also continuing to allow +same-lifetime instances without first addressing this overly coarse +attribute. I have an open RFC to address this, the "Dropck Eyepatch" +RFC; that RFC explains in more detail why this problem arises, using +allocators as a specific motivating use case. + + * Concrete code illustrating this exact example (part of Dropck Eyepatch RFC): + https://github.com/pnkfelix/rfcs/blob/dropck-eyepatch/text/0000-dropck-param-eyepatch.md#example-vect-aallocatordefaultallocator + + * Nonparametric dropck RFC https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md + +#### Standard library containers conclusion + +Rather than wait for the above issues to be resolved, this RFC +proposes that we at least stabilize the `Allocator` trait interface; +then we will at least have a starting point upon which to prototype +standard library integration. + ## Allocators and lifetimes [lifetimes]: #allocators-and-lifetimes From fe88acf2dbb857cdf59c8dff77b35ae1050960d6 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 13:27:04 +0100 Subject: [PATCH 12/37] fix a typo. --- text/0000-kinds-of-allocators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 417f5988779..2c52605e4d2 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -704,7 +704,7 @@ An instance of an allocator has many methods, but an implementor of the trait need only provide two method bodies: [alloc and dealloc][]. (This is only *somewhat* analogous to the `Iterator` trait in Rust. It -is currently very uncommon to override any methods of `Iterator` ecept +is currently very uncommon to override any methods of `Iterator` except for `fn next`. However, I expect it will be much more common for `Allocator` to override at least some of the other methods, like `fn realloc`.) From 2cdb57563e1c81cb25f9e4dc75b3bcea47bf459b Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 13:28:18 +0100 Subject: [PATCH 13/37] Expand the walk-through with some inlining of names of methods being discussed. --- text/0000-kinds-of-allocators.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 2c52605e4d2..83d87db09e2 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -730,7 +730,7 @@ Of course, real-world allocation often needs more than just `alloc`/`dealloc`: in particular, one often wants to avoid extra copying if the existing block of memory can be conceptually expanded in place to meet new allocation needs. In other words, we want -`realloc`, plus alternatives to it that allow clients to avoid +`realloc`, plus alternatives to it (`alloc_excess`) that allow clients to avoid round-tripping through the allocator API. For this, the [memory reuse][] family of methods is appropriate. @@ -743,7 +743,8 @@ let my clients choose how the backing memory is chosen! Why do I have to wrestle with this `Kind` business?" I agree with the sentiment; that's why the `Allocator` trait provides -a family of methods capturing [common usage patterns][]. +a family of methods capturing [common usage patterns][], +for example, `a.alloc_one::()` will return a `Unique` (or error). ## Unchecked variants @@ -758,7 +759,8 @@ via local invariants in their container type). For these clients, the `Allocator` trait provides ["unchecked" variants][unchecked variants] of nearly all of its -methods. +methods; so `a.alloc_unchecked(kind)` will return an `Option
` +(where `None` corresponds to allocation failure). The idea here is that `Allocator` implementors are encouraged to streamline the implmentations of such methods by assuming that all From b6c00503cd8981a292ba0c7121001eb59e579678 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 13:32:36 +0100 Subject: [PATCH 14/37] Make `dealloc` infallibe (i.e. remove Result return-type from `fn dealloc`). --- text/0000-kinds-of-allocators.md | 45 +++++--------------------------- 1 file changed, 6 insertions(+), 39 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 83d87db09e2..dd7185a00a7 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -360,9 +360,8 @@ impl<'a> Allocator for &'a DumbBumpPool { } } - unsafe fn dealloc(&mut self, _ptr: Address, _kind: &Self::Kind) -> Result<(), Self::Error> { + unsafe fn dealloc(&mut self, _ptr: Address, _kind: &Self::Kind) { // this bump-allocator just no-op's on dealloc - Ok(()) } unsafe fn oom(&mut self) -> ! { @@ -1092,13 +1091,6 @@ few motivating examples that *are* clearly feasible and useful. (In fact, most of the "Variations correspond to potentially unresolved questions.) - * Should `dealloc` return a `Result` or not? (Under what - circumstances would we expect `dealloc` to fail in a manner worth - signalling? The main one I can think of is a transient failure, - which was in a previous version of the API but has since been removed. - Still, if errors *can* happen, maybe its best to provide *some* way - for a client to catch them and report them in context.) - * Are the type definitions for `Size`, `Capacity`, `Alignment`, and `Address` an abuse of the `NonZero` type? (Or do we just need some constructor for `NonZero` that asserts that the input is non-zero)? @@ -1772,17 +1764,7 @@ pub unsafe trait Allocator { /// `ptr` must have previously been provided via this allocator, /// and `kind` must *fit* the provided block (see above); /// otherwise yields undefined behavior. - /// - /// Returns `Err` only if deallocation fails in some fashion. - /// In this case callers must assume that ownership of the block has - /// been unrecoverably lost (memory may have been leaked). - /// - /// Note: Implementors are encouraged to avoid `Err`-failure from - /// `dealloc`; most memory allocation APIs do not support - /// signalling failure in their `free` routines, and clients are - /// likely to incorporate that assumption into their own code and - /// just `unwrap` the result of this call. - unsafe fn dealloc(&mut self, ptr: Address, kind: Kind) -> Result<(), Self::Error>; + unsafe fn dealloc(&mut self, ptr: Address, kind: Kind); /// Allocator-specific method for signalling an out-of-memory /// condition. @@ -1913,22 +1895,7 @@ pub unsafe trait Allocator { let result = self.alloc(new_kind); if let Ok(new_ptr) = result { ptr::copy(*ptr as *const u8, *new_ptr, cmp::min(*kind.size(), *new_kind.size())); - if let Err(_) = self.dealloc(ptr, kind) { - // all we can do from the realloc abstraction - // is either: - // - // 1. free the block we just finished copying - // into and pass the error up, - // 2. panic (same as if we had called `unwrap`), - // 3. try to dealloc again, or - // 4. ignore the dealloc error. - // - // They are all terrible; (1.) and (2.) seem unjustifiable, - // and (3.) seems likely to yield an infinite loop (unless - // we add back in some notion of a transient error - // into the API). - // So we choose (4.): ignore the dealloc error. - } + self.dealloc(ptr, kind); } result } @@ -1980,9 +1947,9 @@ pub unsafe trait Allocator { /// Deallocates a block suitable for holding an instance of `T`. /// /// Captures a common usage pattern for allocators. - unsafe fn dealloc_one(&mut self, mut ptr: Unique) -> Result<(), Self::Error> { + unsafe fn dealloc_one(&mut self, mut ptr: Unique) { let raw_ptr = NonZero::new(ptr.get_mut() as *mut T as *mut u8); - self.dealloc(raw_ptr, Kind::new::().unwrap()) + self.dealloc(raw_ptr, Kind::new::().unwrap()); } /// Allocates a block suitable for holding `n` instances of `T`. @@ -2062,7 +2029,7 @@ pub unsafe trait Allocator { /// Otherwise yields undefined behavior. unsafe fn dealloc_unchecked(&mut self, ptr: Address, kind: Kind) { // (default implementation carries checks, but impl's are free to omit them.) - self.dealloc(ptr, kind).unwrap() + self.dealloc(ptr, kind).unwrap(); } /// Returns a pointer suitable for holding data described by From 5138a3214731d401abb9418cf4d477ef75bebc7c Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 13:33:34 +0100 Subject: [PATCH 15/37] some minor rephrasing in the text. --- text/0000-kinds-of-allocators.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index dd7185a00a7..dd009b648f6 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -803,7 +803,7 @@ to both the allocation and deallocation call sites. a simple value or array type is being allocated. * The `alloc_array_unchecked` and `dealloc_array_unchecked` likewise - capture a similar pattern, but are "less safe" in that they put more + capture a common pattern, but are "less safe" in that they put more of an onus on the caller to validate the input parameters before calling the methods. @@ -854,8 +854,7 @@ out-of-memory (OOM) conditions on allocation failure. However, since I also suspect that some programs would benefit from contextual information about *which* allocator is reporting memory exhaustion, I have made `oom` a method of the `Allocator` trait, so -that allocator clients can just call that on error (assuming they want -to trust the failure behavior of the allocator). +that allocator clients have the option of calling that on error. ### Why is `usable_size` ever needed? Why not call `kind.size()` directly, as is done in the default implementation? @@ -1755,8 +1754,14 @@ pub unsafe trait Allocator { /// initialized. (Extension subtraits might restrict this /// behavior, e.g. to ensure initialization.) /// - /// Returns `Err` if allocation fails or if `kind` does + /// Returning `Err` indicates that either memory is exhausted or `kind` does /// not meet allocator's size or alignment constraints. + /// + /// Implementations are encouraged to return `Err` on memory + /// exhaustion rather than panicking or aborting, but this is + /// 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, kind: Kind) -> Result; /// Deallocate the memory referenced by `ptr`. @@ -1774,13 +1779,12 @@ pub unsafe trait Allocator { /// practice this means implementors should eschew allocating, /// especially from `self` (directly or indirectly). /// - /// Implementors of this trait are discouraged from panicking or - /// aborting from other methods in the event of memory exhaustion; + /// Implementions of this trait's allocation methods are discouraged + /// from panicking (or aborting) in the event of memory exhaustion; /// instead they should return an appropriate error from the /// invoked method, and let the client decide whether to invoke /// this `oom` method. unsafe fn oom(&mut self) -> ! { ::core::intrinsics::abort() } - ``` ### Allocator-specific quantities and limits From 4aa94d92d67d0b410f1f36ada85909b3a4eea634 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 13:41:19 +0100 Subject: [PATCH 16/37] alpha-rename `Kind` to `Layout`. (This really was an improvement, if only because it forced me to realize that in some contexts in the text I was using the word "kind" to mean something different than the layout structures being passed around... which sometimes can seem like a nice pun, but overall I suspect it was just a net increase in potential confusion.) --- text/0000-kinds-of-allocators.md | 366 ++++++++++++++++--------------- 1 file changed, 185 insertions(+), 181 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index dd009b648f6..35f7a08eb41 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -115,24 +115,24 @@ individual sections of code.) allocators can inject more specific error types to indicate why an allocation failed. - * The metadata for any allocation is captured in a `Kind` + * The metadata for any allocation is captured in a `Layout` abstraction. This type carries (at minimum) the size and alignment requirements for a memory request. - * The `Kind` type provides a large family of functional construction + * The `Layout` type provides a large family of functional construction methods for building up the description of how memory is laid out. - * Any sized type `T` can be mapped to its `Kind`, via `Kind::new::()`, + * Any sized type `T` can be mapped to its `Layout`, via `Layout::new::()`, - * Heterogenous structure; e.g. `kind1.extend(kind2)`, + * Heterogenous structure; e.g. `layout1.extend(layout2)`, - * Homogenous array types: `kind.repeat(n)` (for `n: usize`), + * Homogenous array types: `layout.repeat(n)` (for `n: usize`), * There are packed and unpacked variants for the latter two methods. * Helper `Allocator` methods like `fn alloc_one` and `fn alloc_array` allow client code to interact with an allocator - without ever directly constructing a `Kind`. + without ever directly constructing a `Layout`. * Once an `Allocator` implementor has the `fn alloc` and `fn dealloc` methods working, it can provide overrides of the other methods, @@ -335,14 +335,14 @@ Here is the demo implementation of `Allocator` for the type. ```rust impl<'a> Allocator for &'a DumbBumpPool { - type Kind = alloc::Kind; + type Layout = alloc::Layout; type Error = BumpAllocError; - unsafe fn alloc(&mut self, kind: &Self::Kind) -> Result { + unsafe fn alloc(&mut self, layout: &Self::Layout) -> Result { let curr = self.avail.load(Ordering::Relaxed) as usize; - let align = *kind.align(); + let align = *layout.align(); let curr_aligned = (curr.overflowing_add(align - 1)) & !(align - 1); - let size = *kind.size(); + let size = *layout.size(); let remaining = (self.end as usize) - curr_aligned; if remaining <= size { return Err(BumpAllocError::MemoryExhausted); @@ -360,7 +360,7 @@ impl<'a> Allocator for &'a DumbBumpPool { } } - unsafe fn dealloc(&mut self, _ptr: Address, _kind: &Self::Kind) { + unsafe fn dealloc(&mut self, _ptr: Address, _layout: &Self::Layout) { // this bump-allocator just no-op's on dealloc } @@ -711,17 +711,17 @@ realloc`.) The `alloc` method returns an `Address` when it succeeds, and `dealloc` takes such an address as its input. But the client must also provide metadata for the allocated block like its size and alignment. -This is encapsulated in the `Kind` argument to `alloc` and `dealloc`. +This is encapsulated in the `Layout` argument to `alloc` and `dealloc`. -### Kinds of allocations +### Memory layouts -A `Kind` just carries the metadata necessary for satisfying an +A `Layout` just carries the metadata necessary for satisfying an allocation request. Its (current, private) representation is just a size and alignment. -The more interesting thing about `Kind` is the -family of public methods associated with it for building new kinds via -composition; these are shown in the [kind api][]. +The more interesting thing about `Layout` is the +family of public methods associated with it for building new layouts via +composition; these are shown in the [layout api][]. ### Reallocation Methods @@ -736,10 +736,10 @@ For this, the [memory reuse][] family of methods is appropriate. ### Type-based Helper Methods -Some readers might skim over the `Kind` API and immediately say "yuck, +Some readers might skim over the `Layout` API and immediately say "yuck, all I wanted to do was allocate some nodes for a tree-structure and let my clients choose how the backing memory is chosen! Why do I have -to wrestle with this `Kind` business?" +to wrestle with this `Layout` business?" I agree with the sentiment; that's why the `Allocator` trait provides a family of methods capturing [common usage patterns][], @@ -758,7 +758,7 @@ via local invariants in their container type). For these clients, the `Allocator` trait provides ["unchecked" variants][unchecked variants] of nearly all of its -methods; so `a.alloc_unchecked(kind)` will return an `Option
` +methods; so `a.alloc_unchecked(layout)` will return an `Option
` (where `None` corresponds to allocation failure). The idea here is that `Allocator` implementors are encouraged @@ -811,12 +811,12 @@ to both the allocation and deallocation call sites. callers who can make use of excess memory to avoid unnecessary calls to `realloc`. -### Why the `Kind` abstraction? +### Why the `Layout` abstraction? While we do want to require clients to hand the allocator the size and alignment, we have found that the code to compute such things follows regular patterns. It makes more sense to factor those patterns out -into a common abstraction; this is what `Kind` provides: a high-level +into a common abstraction; this is what `Layout` provides: a high-level API for describing the memory layout of a composite structure by composing the layout of its subparts. @@ -856,14 +856,14 @@ contextual information about *which* allocator is reporting memory exhaustion, I have made `oom` a method of the `Allocator` trait, so that allocator clients have the option of calling that on error. -### Why is `usable_size` ever needed? Why not call `kind.size()` directly, as is done in the default implementation? +### Why is `usable_size` ever needed? Why not call `layout.size()` directly, as is done in the default implementation? -`kind.size()` returns the minimum required size that the client needs. +`layout.size()` returns the minimum required size that the client needs. In a block-based allocator, this may be less than the *actual* size -that the allocator would ever provide to satisfy that `kind` of +that the allocator would ever provide to satisfy that kind of request. Therefore, `usable_size` provides a way for clients to observe what the minimum actual size of an allocated block for -that`kind` would be, for a given allocator. +that`layout` would be, for a given allocator. (Note that the documentation does say that in general it is better for clients to use `alloc_excess` and `realloc_excess` instead, if they @@ -899,19 +899,23 @@ designs come around. But the same is not true for user-defined allocators: we want to ensure that adding support for them does not inadvertantly kill any chance for adding GC later. -### The inspiration for Kind +### The inspiration for Layout Some aspects of the design of this RFC were selected in the hopes that it would make such integration easier. In particular, the introduction of the relatively high-level `Kind` abstraction was developed, in part, as a way that a GC-aware allocator would build up a tracing -method associated with a kind. +method associated with a layout. Then I realized that the `Kind` abstraction may be valuable on its own, without GC: It encapsulates important patterns when working with representing data as memory records. -So, this RFC offers the `Kind` abstraction without promising that it +(Later we decided to rename `Kind` to `Layout`, in part to avoid +confusion with the use of the word "kind" in the context of +higher-kinded types (HKT).) + +So, this RFC offers the `Layout` abstraction without promising that it solves the GC problem. (It might, or it might not; we don't know yet.) ### Forwards-compatibility @@ -964,7 +968,7 @@ from [RFC PR 39][]. While that is true, it seems like it would be a little short-sighted. In particular, I have neither proven *nor* disproven the value of -`Kind` system described here with respect to GC integration. +`Layout` system described here with respect to GC integration. As far as I know, it is the closest thing we have to a workable system for allowing client code of allocators to accurately describe the @@ -972,15 +976,15 @@ layout of values they are planning to allocate, which is the main ingredient I believe to be necessary for the kind of dynamic reflection that a GC will require of a user-defined allocator. -## Make `Kind` an associated type of `Allocator` trait +## Make `Layout` an associated type of `Allocator` trait -I explored making an `AllocKind` bound and then having +I explored making an `AllocLayout` bound and then having ```rust pub unsafe trait Allocator { /// Describes the sort of records that this allocator can /// construct. - type Kind: AllocKind; + type Layout: AllocLayout; ... } @@ -994,28 +998,28 @@ But the question is: What benefit does it bring? The main one I could imagine is that it might allow us to introduce a division, at the type-system level, between two kinds of allocators: those that are integrated with the GC (i.e., have an associated -`Allocator::Kind` that ensures that all allocated blocks are scannable +`Allocator::Layout` that ensures that all allocated blocks are scannable by a GC) and allocators that are *not* integrated with the GC (i.e., -have an associated `Allocator::Kind` that makes no guarantees about +have an associated `Allocator::Layout` that makes no guarantees about one will know how to scan the allocated blocks. However, no such design has proven itself to be "obviously feasible to -implement," and therefore it would be unreasonable to make the `Kind` +implement," and therefore it would be unreasonable to make the `Layout` an associated type of the `Allocator` trait without having at least a few motivating examples that *are* clearly feasible and useful. -## Variations on the `Kind` API +## Variations on the `Layout` API - * Should `Kind` offer a `fn resize(&self, new_size: usize) -> Kind` constructor method? - (Such a method would rule out deriving GC tracers from kinds; but we could + * Should `Layout` offer a `fn resize(&self, new_size: usize) -> Layout` constructor method? + (Such a method would rule out deriving GC tracers from layouts; but we could maybe provide it as an `unsafe` method.) - * Should `Kind` ensure an invariant that its associated size is + * Should `Layout` ensure an invariant that its associated size is always a multiple of its alignment? * Doing this would allow simplifying a small part of the API, - namely the distinct `Kind::repeat` (returns both a kind and an - offset) versus `Kind::array` (where the offset is derivable from + namely the distinct `Layout::repeat` (returns both a layout and an + offset) versus `Layout::array` (where the offset is derivable from the input `T`). * Such a constraint would have precendent; in particular, the @@ -1027,21 +1031,21 @@ few motivating examples that *are* clearly feasible and useful. invariant implies a certain loss of expressiveness over what we already provide today. - * Should `Kind` ensure an invariant that its associated size is always positive? + * Should `Layout` ensure an invariant that its associated size is always positive? * Pro: Removes something that allocators would need to check about - input kinds (the backing memory allocators will tend to require + input layouts (the backing memory allocators will tend to require that the input sizes are positive). * Con: Requiring positive size means that zero-sized types do not have an associated - `Kind`. That's not the end of the world, but it does make the `Kind` API slightly - less convenient (e.g. one cannot use `extend` with a zero-sized kind to - forcibly inject padding, because zero-sized kinds do not exist). + `Layout`. That's not the end of the world, but it does make the `Layout` API slightly + less convenient (e.g. one cannot use `extend` with a zero-sized layout to + forcibly inject padding, because zero-sized layouts do not exist). - * Should `Kind::align_to` add padding to the associated size? (Probably not; this would + * Should `Layout::align_to` add padding to the associated size? (Probably not; this would make it impossible to express certain kinds of patteerns.) - * Should the `Kind` methods that might "fail" return `Result` instead of `Option`? + * Should the `Layout` methods that might "fail" return `Result` instead of `Option`? ## Variations on the `Allocator` API @@ -1050,15 +1054,15 @@ few motivating examples that *are* clearly feasible and useful. * Clearly `fn dealloc` and `fn realloc` need to be `unsafe`, since feeding in improper inputs could cause unsound behavior. But is there any analogous input to `fn alloc` that could cause - unsoundness (assuming that the `Kind` struct enforces invariants + unsoundness (assuming that the `Layout` struct enforces invariants like "the associated size is non-zero")? * (I left it as `unsafe fn alloc` just to keep the API uniform with `dealloc` and `realloc`.) - * Should `Allocator::realloc` not require that `new_kind.align()` - evenly divide `kind.align()`? In particular, it is not too - expensive to check if the two kinds are not compatible, and fall + * Should `Allocator::realloc` not require that `new_layout.align()` + evenly divide `layout.align()`? In particular, it is not too + expensive to check if the two layouts are not compatible, and fall back on `alloc`/`dealloc` in that case. * Should `Allocator` not provide unchecked variants on `fn alloc`, @@ -1085,7 +1089,7 @@ few motivating examples that *are* clearly feasible and useful. * Since we cannot do `RefCell` (see FIXME above), what is our standard recommendation for what to do instead? - * Should `Kind` be an associated type of `Allocator` (see + * Should `Layout` be an associated type of `Allocator` (see [alternatives][] section for discussion). (In fact, most of the "Variations correspond to potentially unresolved questions.) @@ -1323,25 +1327,25 @@ fn size_align() -> (usize, usize) { ``` -### Kind API -[kind api]: #kind-api +### Layout API +[layout api]: #layout-api ```rust /// Category for a memory record. /// -/// An instance of `Kind` describes a particular layout of memory. -/// You build a `Kind` up as an input to give to an allocator. +/// An instance of `Layout` describes a particular layout of memory. +/// You build a `Layout` up as an input to give to an allocator. /// -/// All kinds have an associated positive size; note that this implies -/// zero-sized types have no corresponding kind. +/// All layouts have an associated positive size; note that this implies +/// zero-sized types have no corresponding layout. #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Kind { +pub struct Layout { // size of the requested block of memory, measured in bytes. size: Size, // alignment of the requested block of memory, measured in bytes. // we ensure that this is always a power-of-two, because API's ///like `posix_memalign` require it and it is a reasonable - // constraint to impose on Kind constructors. + // constraint to impose on Layout constructors. // // (However, we do not analogously require `align >= sizeof(void*)`, // even though that is *also* a requirement of `posix_memalign`.) @@ -1353,59 +1357,59 @@ pub struct Kind { // (potentially switching to overflowing_add and // overflowing_mul as necessary). -impl Kind { +impl Layout { // (private constructor) - fn from_size_align(size: usize, align: usize) -> Kind { + fn from_size_align(size: usize, align: usize) -> Layout { assert!(align.is_power_of_two()); let size = unsafe { assert!(size > 0); NonZero::new(size) }; let align = unsafe { assert!(align > 0); NonZero::new(align) }; - Kind { size: size, align: align } + Layout { size: size, align: align } } - /// The minimum size in bytes for a memory block of this kind. + /// The minimum size in bytes for a memory block of this layout. pub fn size(&self) -> NonZero { self.size } - /// The minimum byte alignment for a memory block of this kind. + /// The minimum byte alignment for a memory block of this layout. pub fn align(&self) -> NonZero { self.align } - /// Constructs a `Kind` suitable for holding a value of type `T`. - /// Returns `None` if no such kind exists (e.g. for zero-sized `T`). + /// Constructs a `Layout` suitable for holding a value of type `T`. + /// Returns `None` if no such layout exists (e.g. for zero-sized `T`). pub fn new() -> Option { let (size, align) = size_align::(); - if size > 0 { Some(Kind::from_size_align(size, align)) } else { None } + if size > 0 { Some(Layout::from_size_align(size, align)) } else { None } } - /// Produces kind describing a record that could be used to + /// Produces layout describing a record that could be used to /// allocate backing structure for `T` (which could be a trait /// or other unsized type like a slice). /// - /// Returns `None` when no such kind exists; for example, when `x` + /// Returns `None` when no such layout exists; for example, when `x` /// is a reference to a zero-sized type. pub fn for_value(t: &T) -> Option { let (size, align) = (mem::size_of_val(t), mem::align_of_val(t)); if size > 0 { - Some(Kind::from_size_align(size, align)) + Some(Layout::from_size_align(size, align)) } else { None } } - /// Creates a kind describing the record that can hold a value - /// of the same kind as `self`, but that also is aligned to + /// Creates a layout describing the record that can hold a value + /// of the same layout as `self`, but that also is aligned to /// alignment `align` (measured in bytes). /// /// If `self` already meets the prescribed alignment, then returns /// `self`. /// /// Note that this method does not add any padding to the overall - /// size, regardless of whether the returned kind has a different + /// size, regardless of whether the returned layout has a different /// alignment. In other words, if `K` has size 16, `K.align_to(32)` /// will *still* have size 16. pub fn align_to(&self, align: Alignment) -> Self { if align > self.align { let pow2_align = align.checked_next_power_of_two().unwrap(); debug_assert!(pow2_align > 0); // (this follows from self.align > 0...) - Kind { align: unsafe { NonZero::new(pow2_align) }, + Layout { align: unsafe { NonZero::new(pow2_align) }, ..*self } } else { *self @@ -1430,11 +1434,11 @@ impl Kind { return len_rounded_up - len; } - /// Creates a kind describing the record for `n` instances of + /// Creates a layout describing the record for `n` instances of /// `self`, with a suitable amount of padding between each to /// ensure that each instance is given its requested size and /// alignment. On success, returns `(k, offs)` where `k` is the - /// kind of the array and `offs` is the distance between the start + /// layout of the array and `offs` is the distance between the start /// of each element in the array. /// /// On zero `n` or arithmetic overflow, returns `None`. @@ -1448,15 +1452,15 @@ impl Kind { None => return None, Some(alloc_size) => alloc_size, }; - Some((Kind::from_size_align(alloc_size, *self.align), padded_size)) + Some((Layout::from_size_align(alloc_size, *self.align), padded_size)) } - /// Creates a kind describing the record for `self` followed by + /// Creates a layout describing the record for `self` followed by /// `next`, including any necessary padding to ensure that `next` - /// will be properly aligned. Note that the result kind will + /// will be properly aligned. Note that the result layout will /// satisfy the alignment properties of both `self` and `next`. /// - /// Returns `Some((k, offset))`, where `k` is kind of the concatenated + /// Returns `Some((k, offset))`, where `k` is layout of the concatenated /// record and `offset` is the relative location, in bytes, of the /// start of the `next` embedded witnin the concatenated record /// (assuming that the record itself starts at offset 0). @@ -1464,14 +1468,14 @@ impl Kind { /// On arithmetic overflow, returns `None`. pub fn extend(&self, next: Self) -> Option<(Self, usize)> { let new_align = unsafe { NonZero::new(cmp::max(*self.align, *next.align)) }; - let realigned = Kind { align: new_align, ..*self }; + let realigned = Layout { align: new_align, ..*self }; let pad = realigned.padding_needed_for(new_align); let offset = *self.size() + pad; let new_size = offset + *next.size(); - Some((Kind::from_size_align(new_size, *new_align), offset)) + Some((Layout::from_size_align(new_size, *new_align), offset)) } - /// Creates a kind describing the record for `n` instances of + /// Creates a layout describing the record for `n` instances of /// `self`, with no padding between each instance. /// /// On zero `n` or overflow, returns `None`. @@ -1481,15 +1485,15 @@ impl Kind { Some(scaled) => scaled, }; let size = unsafe { assert!(scaled > 0); NonZero::new(scaled) }; - Some(Kind { size: size, align: self.align }) + Some(Layout { size: size, align: self.align }) } - /// Creates a kind describing the record for `self` followed by + /// Creates a layout describing the record for `self` followed by /// `next` with no additional padding between the two. Since no /// padding is inserted, the alignment of `next` is irrelevant, - /// and is not incoporated *at all* into the resulting kind. + /// and is not incoporated *at all* into the resulting layout. /// - /// Returns `(k, offset)`, where `k` is kind of the concatenated + /// Returns `(k, offset)`, where `k` is layout of the concatenated /// record and `offset` is the relative location, in bytes, of the /// start of the `next` embedded witnin the concatenated record /// (assuming that the record itself starts at offset 0). @@ -1505,7 +1509,7 @@ impl Kind { Some(new_size) => new_size, }; let new_size = unsafe { NonZero::new(new_size) }; - Some((Kind { size: new_size, ..*self }, *self.size())) + Some((Layout { size: new_size, ..*self }, *self.size())) } // Below family of methods *assume* inputs are pre- or @@ -1513,23 +1517,23 @@ impl Kind { ///do indirectly validate, but that is not part of their /// specification.) // - // Since invalid inputs could yield ill-formed kinds, these + // Since invalid inputs could yield ill-formed layouts, these // methods are `unsafe`. - /// Creates kind describing the record for a single instance of `T`. + /// Creates layout describing the record for a single instance of `T`. /// Requires `T` has non-zero size. pub unsafe fn new_unchecked() -> Self { let (size, align) = size_align::(); - Kind::from_size_align(size, align) + Layout::from_size_align(size, align) } - /// Creates a kind describing the record for `self` followed by + /// Creates a layout describing the record for `self` followed by /// `next`, including any necessary padding to ensure that `next` - /// will be properly aligned. Note that the result kind will + /// will be properly aligned. Note that the result layout will /// satisfy the alignment properties of both `self` and `next`. /// - /// Returns `(k, offset)`, where `k` is kind of the concatenated + /// Returns `(k, offset)`, where `k` is layout of the concatenated /// record and `offset` is the relative location, in bytes, of the /// start of the `next` embedded witnin the concatenated record /// (assuming that the record itself starts at offset 0). @@ -1539,7 +1543,7 @@ impl Kind { self.extend(next).unwrap() } - /// Creates a kind describing the record for `n` instances of + /// 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. @@ -1548,7 +1552,7 @@ impl Kind { self.repeat(n).unwrap() } - /// Creates a kind describing the record for `n` instances of + /// Creates a layout describing the record for `n` instances of /// `self`, with no padding between each instance. /// /// Requires non-zero `n` and no arithmetic overflow from inputs. @@ -1557,12 +1561,12 @@ impl Kind { self.repeat_packed(n).unwrap() } - /// Creates a kind describing the record for `self` followed by + /// Creates a layout describing the record for `self` followed by /// `next` with no additional padding between the two. Since no /// padding is inserted, the alignment of `next` is irrelevant, - /// and is not incoporated *at all* into the resulting kind. + /// and is not incoporated *at all* into the resulting layout. /// - /// Returns `(k, offset)`, where `k` is kind of the concatenated + /// Returns `(k, offset)`, where `k` is layout of the concatenated /// record and `offset` is the relative location, in bytes, of the /// start of the `next` embedded witnin the concatenated record /// (assuming that the record itself starts at offset 0). @@ -1577,11 +1581,11 @@ impl Kind { self.extend_packed(next).unwrap() } - /// Creates a kind describing the record for a `[T; n]`. + /// Creates a layout describing the record for a `[T; n]`. /// /// On zero `n`, zero-sized `T`, or arithmetic overflow, returns `None`. pub fn array(n: usize) -> Option { - Kind::new::() + Layout::new::() .and_then(|k| k.repeat(n)) .map(|(k, offs)| { debug_assert!(offs == mem::size_of::()); @@ -1589,12 +1593,12 @@ impl Kind { }) } - /// Creates a kind describing the record for a `[T; n]`. + /// Creates a layout describing the record for a `[T; n]`. /// /// Requires nonzero `n`, nonzero-sized `T`, and no arithmetic /// overflow; otherwise behavior undefined. pub fn array_unchecked(n: usize) -> Self { - Kind::array::(n).unwrap() + Layout::array::(n).unwrap() } } @@ -1709,17 +1713,17 @@ impl AllocError for AllocErr { ```rust /// An implementation of `Allocator` can allocate, reallocate, and -/// deallocate arbitrary blocks of data described via `Kind`. +/// deallocate arbitrary blocks of data described via `Layout`. /// -/// Some of the methods require that a kind *fit* a memory block. -/// What it means for a kind to "fit" a memory block means is that +/// Some of the methods require that a layout *fit* a memory block. +/// What it means for a layout to "fit" a memory block means is that /// the following two conditions must hold: /// -/// 1. The block's starting address must be aligned to `kind.align()`. +/// 1. The block's starting address must be aligned to `layout.align()`. /// /// 2. The block's size must fall in the range `[use_min, use_max]`, where: /// -/// * `use_min` is `self.usable_size(kind).0`, and +/// * `use_min` is `self.usable_size(layout).0`, and /// /// * `use_max` is the capacity that was (or would have been) /// returned when (if) the block was allocated via a call to @@ -1727,7 +1731,7 @@ impl AllocError for AllocErr { /// /// Note that: /// -/// * the size of the kind most recently used to allocate the block +/// * the size of the layout most recently used to allocate the block /// is guaranteed to be in the range `[use_min, use_max]`, and /// /// * a lower-bound on `use_max` can be safely approximated by a call to @@ -1748,13 +1752,13 @@ pub unsafe trait Allocator { ```rust /// Returns a pointer suitable for holding data described by - /// `kind`, meeting its size and alignment guarantees. + /// `layout`, meeting its size and alignment guarantees. /// /// The returned block of storage may or may not have its contents /// initialized. (Extension subtraits might restrict this /// behavior, e.g. to ensure initialization.) /// - /// Returning `Err` indicates that either memory is exhausted or `kind` does + /// Returning `Err` indicates that either memory is exhausted or `layout` does /// not meet allocator's size or alignment constraints. /// /// Implementations are encouraged to return `Err` on memory @@ -1762,14 +1766,14 @@ pub unsafe trait Allocator { /// 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, kind: Kind) -> Result; + unsafe fn alloc(&mut self, layout: Layout) -> Result; /// Deallocate the memory referenced by `ptr`. /// /// `ptr` must have previously been provided via this allocator, - /// and `kind` must *fit* the provided block (see above); + /// and `layout` must *fit* the provided block (see above); /// otherwise yields undefined behavior. - unsafe fn dealloc(&mut self, ptr: Address, kind: Kind); + unsafe fn dealloc(&mut self, ptr: Address, layout: Layout); /// Allocator-specific method for signalling an out-of-memory /// condition. @@ -1812,10 +1816,10 @@ pub unsafe trait Allocator { fn max_align(&self) -> Option { None } /// Returns bounds on the guaranteed usable size of a successful - /// allocation created with the specified `kind`. + /// allocation created with the specified `layout`. /// - /// In particular, for a given kind `k`, if `usable_size(k)` returns - /// `(l, m)`, then one can use a block of kind `k` as if it has any + /// In particular, for a given layout `k`, if `usable_size(k)` returns + /// `(l, m)`, then one can use a block of layout `k` as if it has any /// size in the range `[l, m]` (inclusive). /// /// (All implementors of `fn usable_size` must ensure that @@ -1835,8 +1839,8 @@ pub unsafe trait Allocator { /// However, for clients that do not wish to track the capacity /// returned by `alloc_excess` locally, this method is likely to /// produce useful results. - unsafe fn usable_size(&self, kind: Kind) -> (Capacity, Capacity) { - (kind.size(), kind.size()) + unsafe fn usable_size(&self, layout: Layout) -> (Capacity, Capacity) { + (layout.size(), layout.size()) } ``` @@ -1849,21 +1853,21 @@ pub unsafe trait Allocator { // realloc. alloc_excess, realloc_excess /// Returns a pointer suitable for holding data described by - /// `new_kind`, meeting its size and alignment guarantees. To + /// `new_layout`, meeting its size and alignment guarantees. To /// accomplish this, this may extend or shrink the allocation - /// referenced by `ptr` to fit `new_kind`. + /// referenced by `ptr` to fit `new_layout`. /// /// * `ptr` must have previously been provided via this allocator. /// - /// * `kind` must *fit* the `ptr` (see above). (The `new_kind` + /// * `layout` must *fit* the `ptr` (see above). (The `new_layout` /// argument need not fit it.) /// /// Behavior undefined if either of latter two constraints are unmet. /// - /// In addition, `new_kind` should not impose a stronger alignment - /// constraint than `kind`. (In other words, `new_kind.align()` - /// must evenly divide `kind.align()`; note this implies the - /// alignment of `new_kind` must not exceed that of `kind`.) + /// In addition, `new_layout` should not impose a stronger alignment + /// constraint than `layout`. (In other words, `new_layout.align()` + /// must evenly divide `layout.align()`; note this implies the + /// alignment of `new_layout` must not exceed that of `layout`.) /// However, behavior is well-defined (though underspecified) when /// this constraint is violated; further discussion below. /// @@ -1874,12 +1878,12 @@ pub unsafe trait Allocator { /// transferred back to the caller again via the return value of /// this method). /// - /// Returns `Err` only if `new_kind` does not meet the allocator's + /// Returns `Err` only if `new_layout` does not meet the allocator's /// size and alignment constraints of the allocator or the - /// alignment of `kind`, or if reallocation otherwise fails. (Note + /// alignment of `layout`, or if reallocation otherwise fails. (Note /// that did not say "if and only if" -- in particular, an /// implementation of this method *can* return `Ok` if - /// `new_kind.align() > old_kind.align()`; or it can return `Err` + /// `new_layout.align() > old_layout.align()`; or it can return `Err` /// in that scenario.) /// /// If this method returns `Err`, then ownership of the memory @@ -1887,40 +1891,40 @@ pub unsafe trait Allocator { /// contents of the memory block are unaltered. unsafe fn realloc(&mut self, ptr: Address, - kind: Kind, - new_kind: Kind) -> Result { - let (min, max) = self.usable_size(kind); - let s = new_kind.size(); - // All Kind alignments are powers of two, so a comparison + layout: Layout, + new_layout: Layout) -> Result { + let (min, max) = self.usable_size(layout); + 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_kind.align() <= kind.align() { + if min <= s && s <= max && new_layout.align() <= layout.align() { return Ok(ptr); } else { - let result = self.alloc(new_kind); + let result = self.alloc(new_layout); if let Ok(new_ptr) = result { - ptr::copy(*ptr as *const u8, *new_ptr, cmp::min(*kind.size(), *new_kind.size())); - self.dealloc(ptr, kind); + ptr::copy(*ptr as *const u8, *new_ptr, cmp::min(*layout.size(), *new_layout.size())); + self.dealloc(ptr, layout); } result } } /// Behaves like `fn alloc`, but also returns the whole size of - /// the returned block. For some `kind` inputs, like arrays, this + /// the returned block. For some `layout` inputs, like arrays, this /// may include extra storage usable for additional data. - unsafe fn alloc_excess(&mut self, kind: Kind) -> Result { - self.alloc(kind).map(|p| Excess(p, self.usable_size(kind).1)) + unsafe fn alloc_excess(&mut self, layout: Layout) -> Result { + self.alloc(layout).map(|p| Excess(p, self.usable_size(layout).1)) } /// Behaves like `fn realloc`, but also returns the whole size of - /// the returned block. For some `kind` inputs, like arrays, this + /// the returned block. For some `layout` inputs, like arrays, this /// may include extra storage usable for additional data. unsafe fn realloc_excess(&mut self, ptr: Address, - kind: Kind, - new_kind: Kind) -> Result { - self.realloc(ptr, kind, new_kind) - .map(|p| Excess(p, self.usable_size(new_kind).1)) + layout: Layout, + new_layout: Layout) -> Result { + self.realloc(ptr, layout, new_layout) + .map(|p| Excess(p, self.usable_size(new_layout).1)) } ``` @@ -1939,7 +1943,7 @@ pub unsafe trait Allocator { /// The returned block is suitable for passing to the /// `alloc`/`realloc` methods of this allocator. unsafe fn alloc_one(&mut self) -> Result, Self::Error> { - if let Some(k) = Kind::new::() { + if let Some(k) = Layout::new::() { self.alloc(k).map(|p|Unique::new(*p as *mut T)) } else { // (only occurs for zero-sized T) @@ -1953,7 +1957,7 @@ pub unsafe trait Allocator { /// Captures a common usage pattern for allocators. unsafe fn dealloc_one(&mut self, mut ptr: Unique) { let raw_ptr = NonZero::new(ptr.get_mut() as *mut T as *mut u8); - self.dealloc(raw_ptr, Kind::new::().unwrap()); + self.dealloc(raw_ptr, Layout::new::().unwrap()); } /// Allocates a block suitable for holding `n` instances of `T`. @@ -1963,8 +1967,8 @@ pub unsafe trait Allocator { /// The returned block is suitable for passing to the /// `alloc`/`realloc` methods of this allocator. unsafe fn alloc_array(&mut self, n: usize) -> Result, Self::Error> { - match Kind::array::(n) { - Some(kind) => self.alloc(kind).map(|p|Unique::new(*p as *mut T)), + match Layout::array::(n) { + Some(layout) => self.alloc(layout).map(|p|Unique::new(*p as *mut T)), None => Err(Self::Error::invalid_input()), } } @@ -1981,7 +1985,7 @@ pub unsafe trait Allocator { ptr: Unique, n_old: usize, n_new: usize) -> Result, Self::Error> { - let old_new_ptr = (Kind::array::(n_old), Kind::array::(n_new), *ptr); + let old_new_ptr = (Layout::array::(n_old), Layout::array::(n_new), *ptr); if let (Some(k_old), Some(k_new), ptr) = old_new_ptr { self.realloc(NonZero::new(ptr as *mut u8), k_old, k_new) .map(|p|Unique::new(*p as *mut T)) @@ -1995,7 +1999,7 @@ pub unsafe trait Allocator { /// Captures a common usage pattern for allocators. unsafe fn dealloc_array(&mut self, ptr: Unique, n: usize) -> Result<(), Self::Error> { let raw_ptr = NonZero::new(*ptr as *mut u8); - if let Some(k) = Kind::array::(n) { + if let Some(k) = Layout::array::(n) { self.dealloc(raw_ptr, k) } else { Err(Self::Error::invalid_input()) @@ -2011,7 +2015,7 @@ pub unsafe trait Allocator { // UNCHECKED METHOD VARIANTS /// Returns a pointer suitable for holding data described by - /// `kind`, meeting its size and alignment guarantees. + /// `layout`, meeting its size and alignment guarantees. /// /// The returned block of storage may or may not have its contents /// initialized. (Extension subtraits might restrict this @@ -2021,25 +2025,25 @@ pub unsafe trait Allocator { /// /// Behavior undefined if input does not meet size or alignment /// constraints of this allocator. - unsafe fn alloc_unchecked(&mut self, kind: Kind) -> Option
{ + unsafe fn alloc_unchecked(&mut self, layout: Layout) -> Option
{ // (default implementation carries checks, but impl's are free to omit them.) - self.alloc(kind).ok() + self.alloc(layout).ok() } /// Deallocate the memory referenced by `ptr`. /// /// `ptr` must have previously been provided via this allocator, - /// and `kind` must *fit* the provided block (see above). + /// and `layout` must *fit* the provided block (see above). /// Otherwise yields undefined behavior. - unsafe fn dealloc_unchecked(&mut self, ptr: Address, kind: Kind) { + unsafe fn dealloc_unchecked(&mut self, ptr: Address, layout: Layout) { // (default implementation carries checks, but impl's are free to omit them.) - self.dealloc(ptr, kind).unwrap(); + self.dealloc(ptr, layout).unwrap(); } /// Returns a pointer suitable for holding data described by - /// `new_kind`, meeting its size and alignment guarantees. To + /// `new_layout`, meeting its size and alignment guarantees. To /// accomplish this, may extend or shrink the allocation - /// referenced by `ptr` to fit `new_kind`. + /// referenced by `ptr` to fit `new_layout`. //// /// (In other words, ownership of the memory block associated with /// `ptr` is first transferred back to this allocator, but the @@ -2048,12 +2052,12 @@ pub unsafe trait Allocator { /// /// * `ptr` must have previously been provided via this allocator. /// - /// * `kind` must *fit* the `ptr` (see above). (The `new_kind` + /// * `layout` must *fit* the `ptr` (see above). (The `new_layout` /// argument need not fit it.) /// - /// * `new_kind` must meet the allocator's size and alignment - /// constraints. In addition, `new_kind.align()` must equal - /// `kind.align()`. (Note that this is a stronger constraint + /// * `new_layout` must meet the allocator's size and alignment + /// constraints. In addition, `new_layout.align()` must equal + /// `layout.align()`. (Note that this is a stronger constraint /// that that imposed by `fn realloc`.) /// /// Behavior undefined if any of latter three constraints are unmet. @@ -2065,25 +2069,25 @@ pub unsafe trait Allocator { /// original memory block referenced by `ptr` is unaltered. unsafe fn realloc_unchecked(&mut self, ptr: Address, - kind: Kind, - new_kind: Kind) -> Option
{ + layout: Layout, + new_layout: Layout) -> Option
{ // (default implementation carries checks, but impl's are free to omit them.) - self.realloc(ptr, kind, new_kind).ok() + self.realloc(ptr, layout, new_layout).ok() } /// Behaves like `fn alloc_unchecked`, but also returns the whole /// size of the returned block. - unsafe fn alloc_excess_unchecked(&mut self, kind: Kind) -> Option { - self.alloc_excess(kind).ok() + unsafe fn alloc_excess_unchecked(&mut self, layout: Layout) -> Option { + self.alloc_excess(layout).ok() } /// Behaves like `fn realloc_unchecked`, but also returns the /// whole size of the returned block. unsafe fn realloc_excess_unchecked(&mut self, ptr: Address, - kind: Kind, - new_kind: Kind) -> Option { - self.realloc_excess(ptr, kind, new_kind).ok() + layout: Layout, + new_layout: Layout) -> Option { + self.realloc_excess(ptr, layout, new_layout).ok() } @@ -2095,8 +2099,8 @@ pub unsafe trait Allocator { /// overflow, and `T` is not zero sized; otherwise yields /// undefined behavior. unsafe fn alloc_array_unchecked(&mut self, n: usize) -> Option> { - let kind = Kind::array_unchecked::(n); - self.alloc_unchecked(kind).map(|p|Unique::new(*p as *mut T)) + let layout = Layout::array_unchecked::(n); + self.alloc_unchecked(layout).map(|p|Unique::new(*p as *mut T)) } /// Reallocates a block suitable for holding `n_old` instances of `T`, @@ -2111,8 +2115,8 @@ pub unsafe trait Allocator { ptr: Unique, n_old: usize, n_new: usize) -> Option> { - let (k_old, k_new, ptr) = (Kind::array_unchecked::(n_old), - Kind::array_unchecked::(n_new), + let (k_old, k_new, ptr) = (Layout::array_unchecked::(n_old), + Layout::array_unchecked::(n_new), *ptr); self.realloc_unchecked(NonZero::new(ptr as *mut u8), k_old, k_new) .map(|p|Unique::new(*p as *mut T)) @@ -2126,8 +2130,8 @@ pub unsafe trait Allocator { /// overflow, and `T` is not zero sized; otherwise yields /// undefined behavior. unsafe fn dealloc_array_unchecked(&mut self, ptr: Unique, n: usize) { - let kind = Kind::array_unchecked::(n); - self.dealloc_unchecked(NonZero::new(*ptr as *mut u8), kind); + let layout = Layout::array_unchecked::(n); + self.dealloc_unchecked(NonZero::new(*ptr as *mut u8), layout); } } ``` From e2d461cdffe306cf2b62da94c1456319c28f2a95 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 14:15:27 +0100 Subject: [PATCH 17/37] revised `fn oom` interface to also take the `Self::Error` as input, so that contextual information can be fed back into the allocator itself. Though on further review, the comment that inspired this, https://github.com/rust-lang/rfcs/pull/1398#issuecomment-169203800 also wanted a `FormatArgs` argument too, so that the client code could feed back in arbitrary info. --- text/0000-kinds-of-allocators.md | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 35f7a08eb41..ec8cdf9f435 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -307,7 +307,7 @@ will expose: ```rust #[derive(Copy, Clone, PartialEq, Eq, Debug)] -enum BumpAllocError { Invalid, MemoryExhausted, Interference } +enum BumpAllocError { Invalid, MemoryExhausted(alloc::Layout), Interference } impl BumpAllocError { fn is_transient(&self) { *self == BumpAllocError::Interference } @@ -315,7 +315,7 @@ impl BumpAllocError { impl alloc::AllocError for BumpAllocError { fn invalid_input() -> Self { BumpAllocError::MemoryExhausted } - fn is_memory_exhausted(&self) -> bool { *self == BumpAllocError::MemoryExhausted } + fn is_memory_exhausted(&self) -> bool { if let BumpAllocError::MemoryExhausted(_) = *self { true } else { false } } fn is_request_unsupported(&self) -> bool { false } } ``` @@ -335,17 +335,16 @@ Here is the demo implementation of `Allocator` for the type. ```rust impl<'a> Allocator for &'a DumbBumpPool { - type Layout = alloc::Layout; type Error = BumpAllocError; - unsafe fn alloc(&mut self, layout: &Self::Layout) -> Result { + unsafe fn alloc(&mut self, layout: &alloc::Layout) -> Result { let curr = self.avail.load(Ordering::Relaxed) as usize; let align = *layout.align(); let curr_aligned = (curr.overflowing_add(align - 1)) & !(align - 1); let size = *layout.size(); let remaining = (self.end as usize) - curr_aligned; if remaining <= size { - return Err(BumpAllocError::MemoryExhausted); + return Err(BumpAllocError::MemoryExhausted(layout.clone())); } let curr = curr as *mut u8; @@ -360,12 +359,12 @@ impl<'a> Allocator for &'a DumbBumpPool { } } - unsafe fn dealloc(&mut self, _ptr: Address, _layout: &Self::Layout) { + unsafe fn dealloc(&mut self, _ptr: Address, _layout: &alloc::Layout) { // this bump-allocator just no-op's on dealloc } - unsafe fn oom(&mut self) -> ! { - panic!("exhausted memory in {}", self.name); + fn oom(&mut self, err: Self::Error) -> ! { + panic!("exhausted memory in {} on request {:?}", self.name, err); } } @@ -1098,10 +1097,6 @@ few motivating examples that *are* clearly feasible and useful. `Address` an abuse of the `NonZero` type? (Or do we just need some constructor for `NonZero` that asserts that the input is non-zero)? - * Should `fn oom(&self)` take in more arguments (e.g. to allow the - client to provide more contextual information about the OOM - condition)? - * Should we get rid of the `AllocError` bound entirely? Is the given set of methods actually worth providing to all generic clients? @@ -1778,8 +1773,8 @@ pub unsafe trait Allocator { /// Allocator-specific method for signalling an out-of-memory /// condition. /// - /// Any activity done by the `oom` method should ensure that it - /// does not infinitely regress in nested calls to `oom`. In + /// Implementations of the `oom` method are discouraged from + /// infinitely regressing in nested calls to `oom`. In /// practice this means implementors should eschew allocating, /// especially from `self` (directly or indirectly). /// @@ -1788,7 +1783,7 @@ pub unsafe trait Allocator { /// instead they should return an appropriate error from the /// invoked method, and let the client decide whether to invoke /// this `oom` method. - unsafe fn oom(&mut self) -> ! { ::core::intrinsics::abort() } + fn oom(&mut self, _: Self::Error) -> ! { ::core::intrinsics::abort() } ``` ### Allocator-specific quantities and limits From 05c963505048e3141e8c54b64811364f13f7d2b1 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 14:22:40 +0100 Subject: [PATCH 18/37] Expanded docs for `AllocError` trait's methods. Added unresolved Q about the new `fn oom` method. --- text/0000-kinds-of-allocators.md | 39 +++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index ec8cdf9f435..6d535f0822b 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1118,6 +1118,11 @@ few motivating examples that *are* clearly feasible and useful. low-level allocators will behave well for large alignments. See https://github.com/rust-lang/rust/issues/30170 + * Should `Allocator::oom` also take a `std::fmt::Arguments<'a>` parameter + so that clients can feed in context-specific information that is not + part of the original input `Layout` argument? (I have not done this + mainly because I do not want to introduce a dependency on `libstd`.) + # Change History * Changed `fn usable_size` to return `(l, m)` rathern than just `m`. @@ -1609,15 +1614,19 @@ pub trait AllocError { /// Construct an error that indicates operation failure due to /// invalid input values for the request. /// - /// This can be used, for example, to signal an overflow occurred - /// during arithmetic computation. (However, since overflows - /// frequently represent an allocation attempt that would exhaust - /// memory, clients are alternatively allowed to constuct an error - /// representing memory exhaustion in such scenarios.) + /// This can be used, for example, to signal that allocation of + /// a zero-sized type was requested. + /// + /// As another example, it might be used to signal that an overflow + /// occurred during arithmetic computation with the input. (However, + /// since overflows can also occur during large allocation requests + /// that would exhaust memory if arbitrary-precision arithmetic were + /// used, clients are alternatively allowed to constuct an error + /// representing memory exhaustion in this scenario.) fn invalid_input() -> Self; /// Returns true if the error is due to hitting some resource - /// limit or otherwise running out of memory. This condition + /// limit, or otherwise running out of memory. This condition /// serves as a hint that some series of deallocations *might* /// allow a subsequent reissuing of the original allocation /// request to succeed. @@ -1626,23 +1635,27 @@ pub trait AllocError { /// e.g. usually when `malloc` returns `null`, it is because of /// hitting a user resource limit or system memory exhaustion. /// - /// Note that the resource exhaustion could be specific to the + /// Note that the resource exhaustion could be internal to the /// original allocator (i.e. the only way to free up memory is by /// deallocating memory attached to that allocator), or it could - /// be associated with some other state outside of the original - /// alloactor. The `AllocError` trait does not distinguish between - /// the two scenarios. + /// be associated with some other state external to the original + /// allocator (e.g. freeing up memory or reducing fragmentation + /// globally might allow a call to the system `malloc` to succeed). + /// The `AllocError` trait does not distinguish between the two + /// scenarios (but instances of the associated `Allocator::Error` + /// type might provide ways to distinguish them). /// /// Finally, error responses to allocation input requests that are /// *always* illegal for *any* allocator (e.g. zero-sized or /// arithmetic-overflowing requests) are allowed to respond `true` - /// here. (This is to allow `MemoryExhausted` as a valid error type - /// for an allocator that can handle all "sane" requests.) + /// here. (This is to allow `MemoryExhausted` as a valid + /// zero-sized error type for an allocator that can handle all + /// "sane" requests.) fn is_memory_exhausted(&self) -> bool; /// Returns true if the allocator is fundamentally incapable of /// satisfying the original request. This condition implies that - /// such an allocation request will never succeed on this + /// such an allocation request would never succeed on *this* /// allocator, regardless of environment, memory pressure, or /// other contextual condtions. /// From a93abd7defe72526e182cdcf0b06136b57292654 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 14:24:24 +0100 Subject: [PATCH 19/37] made `Layout` (formerly `Kind`) non-`Copy`. --- text/0000-kinds-of-allocators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 6d535f0822b..e8ca9a40178 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1338,7 +1338,7 @@ fn size_align() -> (usize, usize) { /// /// All layouts have an associated positive size; note that this implies /// zero-sized types have no corresponding layout. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Layout { // size of the requested block of memory, measured in bytes. size: Size, From cad7d43997dcc77092e512d886bd2528f630c246 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 14:24:54 +0100 Subject: [PATCH 20/37] expanded change history to note latest changes. --- text/0000-kinds-of-allocators.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index e8ca9a40178..7cc94e6ef17 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1130,6 +1130,13 @@ few motivating examples that *are* clearly feasible and useful. * Removed `fn is_transient` from `trait AllocError`, and removed discussion of transient errors from the API. +* Made `fn dealloc` method infallible (i.e. removed its `Result` return type). + +* Alpha-renamed `alloc::Kind` type to `alloc::Layout`, and made it non-`Copy`. + +* Revised `fn oom` method to take the `Self::Error` as an input (so that the + allocator can, indirectly, feed itself information about what went wrong). + # Appendices ## Bibliography From 5ae80387569178808fba81a4ce01666750e2e1e5 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 15:03:34 +0100 Subject: [PATCH 21/37] added discussion of why API uses `&mut self` (rather than `&self` or `self`). --- text/0000-kinds-of-allocators.md | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 7cc94e6ef17..8391e04e023 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1048,6 +1048,45 @@ few motivating examples that *are* clearly feasible and useful. ## Variations on the `Allocator` API + * Should the allocator methods take `&self` or `self` rather than `&mut self`. + + As noted during in the RFC comments, nearly every trait goes through a bit + of an identity crisis in terms of deciding what kind of `self` parameter is + appropriate. + + The justification for `&mut self` is this: + + * It does not restrict allocator implementors from making sharable allocators: + to do so, just do `impl<'a> Allocator for &'a MySharedAlloc`, as illustrated + in the `DumbBumpPool` example. + + * `&mut self` is better than `&self` for simple allocators that are *not* sharable. + `&mut self` ensures that the allocation methods have exclusive + access to the underlying allocator state, without resorting to a + lock. (Another way of looking at it: It moves the onus of using a + lock outward, to the allocator clients.) + + * One might think that the points made + above apply equally well to `self` (i.e., if you want to implement an allocator + that wants to take itself via a `&mut`-reference when the methods take `self`, + then do `impl<'a> Allocator for &'a mut MyUniqueAlloc`). + + However, the problem with `self` is that if you want to use an + allocator for *more than one* allocation, you will need to call + `clone()` (or make the allocator parameter implement + `Copy`). This means in practice all allocators will need to + support `Clone` (and thus support sharing in general, as + discussed in the [Allocators and lifetimes][lifetimes] section). + + Put more simply, requiring that allocators implement `Clone` means + that it will *not* be pratical to do + `impl<'a> Allocator for &'a mut MyUniqueAlloc`. + + By using `&mut self` for the allocation methods, we can encode + the expected use case of an *unshared* allocator that is used + repeatedly in a linear fashion (e.g. vector that needs to + reallocate its backing storage). + * Should `Allocator::alloc` be safe instead of `unsafe fn`? * Clearly `fn dealloc` and `fn realloc` need to be `unsafe`, since From dd485fd688b8b55afb2971ee681ff1250452cb76 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 15:14:19 +0100 Subject: [PATCH 22/37] amend discussion of `&mut self` with explicit note about why `impl Allocator for &mut MyUniqAlloc` cannot just rely on reborrows to handle satisfying `self` parameters. --- text/0000-kinds-of-allocators.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 8391e04e023..8a8b3bceaf0 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1078,6 +1078,11 @@ few motivating examples that *are* clearly feasible and useful. support `Clone` (and thus support sharing in general, as discussed in the [Allocators and lifetimes][lifetimes] section). + (Remember, I'm thinking about allocator-parametric code like + `Vec`, which does not know if the `A` is a + `&mut`-reference. In that context, therefore one cannot assume + that reborrowing machinery is available to the client code.) + Put more simply, requiring that allocators implement `Clone` means that it will *not* be pratical to do `impl<'a> Allocator for &'a mut MyUniqueAlloc`. From d9232de6c6b1983ef4095cbb449489954cb83013 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 15:16:22 +0100 Subject: [PATCH 23/37] added very short discussion of why there's no lifetime-enriched `Address<'a>`. --- text/0000-kinds-of-allocators.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 8a8b3bceaf0..f5498a37721 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1092,6 +1092,18 @@ few motivating examples that *are* clearly feasible and useful. repeatedly in a linear fashion (e.g. vector that needs to reallocate its backing storage). + * Should the types representing allocated storage have lifetimes attached? + (E.g. `fn alloc<'a>(&mut self, layout: &alloc::Layout) -> Address<'a>`.) + + I think Gankro [put it best](https://github.com/rust-lang/rfcs/pull/1398#issuecomment-164003160): + + > This is a low-level unsafe interface, and the expected usecases make it + > both quite easy to avoid misuse, and impossible to use lifetimes + > (you want a struct to store the allocator and the allocated elements). + > Any time we've tried to shove more lifetimes into these kinds of + > interfaces have just been an annoying nuisance necessitating + > copy-lifetime/transmute nonsense. + * Should `Allocator::alloc` be safe instead of `unsafe fn`? * Clearly `fn dealloc` and `fn realloc` need to be `unsafe`, since From 2f3034a7c401f98f1c33a74de7e24e3a32b97d74 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 16 Mar 2016 18:49:43 +0100 Subject: [PATCH 24/37] Added extensive discussion of zero-sized allocations to the alternatives section. --- text/0000-kinds-of-allocators.md | 49 ++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index f5498a37721..0c7719ba31d 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1138,6 +1138,42 @@ few motivating examples that *are* clearly feasible and useful. (But the resulting uniformity of the whole API might shift the balance to "worth it".) + * Should the precondition of allocation methods be loosened to + accept zero-sized types? + + Right now, there is a requirement that the allocation requests + denote non-zero sized types (this requirement is encoded in two + ways: for `Layout`-consuming methods like `alloc`, it is enforced + via the invariant that the `Size` is a `NonZero`, and this is + enforced by checks in the `Layout` construction code; for the + convenience methods like `alloc_one`, they will return `Err` if the + allocation request is zero-sized). + + The main motivation for this restriction is some underlying system + allocators, like `jemalloc`, explicitly disallow zero-sized + inputs. Therefore, to remove all unnecessary control-flow branches + between the client and the underlying allocator, the `Allocator` + trait is bubbling that restriction up and imposing it onto the + clients, who will presumably enforce this invariant via + container-specific means. + + But: pre-existing container types (like `Vec`) already + *allow* zero-sized `T`. Therefore, there is an unfortunate mismatch + between the ideal API those container would prefer for their + allocators and the actual service that this `Allocator` trait is + providing. + + So: Should we lift this precondition of the allocation methods, and allow + zero-sized requests (which might be handled by a global sentinel value, or + by an allocator-specific sentinel value, or via some other means -- this + would have to be specified as part of the Allocator API)? + + (As a middle ground, we could lift the precondition solely for the convenience + methods like `fn alloc_one` and `fn alloc_array`; that way, the most low-level + methods like `fn alloc` would continue to minimize the overhead they add + over the underlying system allocator, while the convenience methods would truly + be convenient.) + # Unresolved questions [unresolved]: #unresolved-questions @@ -2000,8 +2036,8 @@ pub unsafe trait Allocator { ``` -### Allocator common usage patterns -[common usage patterns]: #allocator-common-usage-patterns +### Allocator convenience methods for common usage patterns +[common usage patterns]: #allocator-convenience-methods-for-common-usage-patterns ```rust // == COMMON USAGE PATTERNS == @@ -2013,6 +2049,8 @@ pub unsafe trait Allocator { /// /// The returned block is suitable for passing to the /// `alloc`/`realloc` methods of this allocator. + /// + /// Returns `Err` for zero-sized `T`. unsafe fn alloc_one(&mut self) -> Result, Self::Error> { if let Some(k) = Layout::new::() { self.alloc(k).map(|p|Unique::new(*p as *mut T)) @@ -2025,6 +2063,11 @@ pub unsafe trait Allocator { /// Deallocates a block suitable for holding an instance of `T`. /// + /// The given block must have been produced by this allocator, + /// and must be suitable for storing a `T` (in terms of alignment + /// as well as minimum and maximum size); otherwise yields + /// undefined behavior. + /// /// Captures a common usage pattern for allocators. unsafe fn dealloc_one(&mut self, mut ptr: Unique) { let raw_ptr = NonZero::new(ptr.get_mut() as *mut T as *mut u8); @@ -2037,6 +2080,8 @@ pub unsafe trait Allocator { /// /// The returned block is suitable for passing to the /// `alloc`/`realloc` methods of this allocator. + /// + /// Returns `Err` for zero-sized `T` or `n == 0`. unsafe fn alloc_array(&mut self, n: usize) -> Result, Self::Error> { match Layout::array::(n) { Some(layout) => self.alloc(layout).map(|p|Unique::new(*p as *mut T)), From 3a597f92854f194621f57282bd03a37ccc72cd3d Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 17 Mar 2016 18:08:14 +0100 Subject: [PATCH 25/37] Fix code to reflect that `fn dealloc` method no longer returns `Result`. Remove `fn dealloc_unchecked` method since it no longer provides any benefit over `fn dealloc` anymore, since `fn dealloc` no longer has any preconditions to check (*). (*) Or at least, if it *does* choose to check preconditions (like "was the address part of my set of memory blocks?"), there is no longer way for `fn dealloc` to signal an error condition besides `panic`, and I do not think trying to prepare for that hypothetical scenario is worth adding the `fn dealloc_unchecked` method to the API. --- text/0000-kinds-of-allocators.md | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 0c7719ba31d..cb08fdb8ca3 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -746,14 +746,14 @@ for example, `a.alloc_one::()` will return a `Unique` (or error). ## Unchecked variants -Finally, all of the methods above return `Result`, and guarantee some +Finally, almost all of the methods above return `Result`, and guarantee some amount of input validation. (This is largely because I observed code duplication doing such validation on the client side; or worse, such validation accidentally missing.) However, some clients will want to bypass such checks (and do it -without risking undefined behavior by ensuring the preconditions hold -via local invariants in their container type). +without risking undefined behavior, namely by ensuring the method preconditions +hold via local invariants in their container type). For these clients, the `Allocator` trait provides ["unchecked" variants][unchecked variants] of nearly all of its @@ -2116,7 +2116,8 @@ pub unsafe trait Allocator { unsafe fn dealloc_array(&mut self, ptr: Unique, n: usize) -> Result<(), Self::Error> { let raw_ptr = NonZero::new(*ptr as *mut u8); if let Some(k) = Layout::array::(n) { - self.dealloc(raw_ptr, k) + self.dealloc(raw_ptr, k); + Ok(()) } else { Err(Self::Error::invalid_input()) } @@ -2146,16 +2147,6 @@ pub unsafe trait Allocator { self.alloc(layout).ok() } - /// Deallocate the memory referenced by `ptr`. - /// - /// `ptr` must have previously been provided via this allocator, - /// and `layout` must *fit* the provided block (see above). - /// Otherwise yields undefined behavior. - unsafe fn dealloc_unchecked(&mut self, ptr: Address, layout: Layout) { - // (default implementation carries checks, but impl's are free to omit them.) - self.dealloc(ptr, layout).unwrap(); - } - /// Returns a pointer suitable for holding data described by /// `new_layout`, meeting its size and alignment guarantees. To /// accomplish this, may extend or shrink the allocation From 40c84c2664a08cb6b208371d2c9ac3c15cf79de1 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 17 Mar 2016 18:09:41 +0100 Subject: [PATCH 26/37] fixed oversight (should have been part of previous commit). --- text/0000-kinds-of-allocators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index cb08fdb8ca3..35d6060a6c9 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -2238,7 +2238,7 @@ pub unsafe trait Allocator { /// undefined behavior. unsafe fn dealloc_array_unchecked(&mut self, ptr: Unique, n: usize) { let layout = Layout::array_unchecked::(n); - self.dealloc_unchecked(NonZero::new(*ptr as *mut u8), layout); + self.dealloc(NonZero::new(*ptr as *mut u8), layout); } } ``` From 06e226374b1736d6dff2ad642390382c7778283e Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 17 Mar 2016 18:13:49 +0100 Subject: [PATCH 27/37] "fixed" `oom` default method impl (the `abort` intrinisic requires `unsafe`) (Though to be honest revisiting this made me wonder if I should just require clients to implement `fn oom` themselves.) --- text/0000-kinds-of-allocators.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 35d6060a6c9..6da03b15dc7 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1895,7 +1895,9 @@ pub unsafe trait Allocator { /// instead they should return an appropriate error from the /// invoked method, and let the client decide whether to invoke /// this `oom` method. - fn oom(&mut self, _: Self::Error) -> ! { ::core::intrinsics::abort() } + fn oom(&mut self, _: Self::Error) -> ! { + unsafe { ::core::intrinsics::abort() } + } ``` ### Allocator-specific quantities and limits From 34d019d19e021a1506aee3933ddffbf14a858e1d Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 17 Mar 2016 18:23:28 +0100 Subject: [PATCH 28/37] Updated implementation to reflect that `Layout` (nee `Kind`) is no longer `Copy`. (I should have done this as part of the earlier commit that removed `deriving(Copy)` from `Layout`.) --- text/0000-kinds-of-allocators.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 6da03b15dc7..885f6c505ea 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1511,7 +1511,7 @@ impl Layout { Layout { align: unsafe { NonZero::new(pow2_align) }, ..*self } } else { - *self + self.clone() } } @@ -1948,7 +1948,7 @@ pub unsafe trait Allocator { /// However, for clients that do not wish to track the capacity /// returned by `alloc_excess` locally, this method is likely to /// produce useful results. - unsafe fn usable_size(&self, layout: Layout) -> (Capacity, Capacity) { + unsafe fn usable_size(&self, layout: &Layout) -> (Capacity, Capacity) { (layout.size(), layout.size()) } @@ -2002,16 +2002,18 @@ pub unsafe trait Allocator { ptr: Address, layout: Layout, new_layout: Layout) -> Result { - let (min, max) = self.usable_size(layout); + let (min, max) = self.usable_size(&layout); 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() { return Ok(ptr); } else { + let new_size = new_layout.size(); + 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(*layout.size(), *new_layout.size())); + ptr::copy(*ptr as *const u8, *new_ptr, cmp::min(*old_size, *new_size)); self.dealloc(ptr, layout); } result @@ -2022,7 +2024,8 @@ pub unsafe trait Allocator { /// the returned block. For some `layout` inputs, like arrays, this /// may include extra storage usable for additional data. unsafe fn alloc_excess(&mut self, layout: Layout) -> Result { - self.alloc(layout).map(|p| Excess(p, self.usable_size(layout).1)) + let usable_size = self.usable_size(&layout); + self.alloc(layout).map(|p| Excess(p, usable_size.1)) } /// Behaves like `fn realloc`, but also returns the whole size of @@ -2032,8 +2035,9 @@ pub unsafe trait Allocator { ptr: Address, layout: Layout, new_layout: Layout) -> Result { + let usable_size = self.usable_size(&new_layout); self.realloc(ptr, layout, new_layout) - .map(|p| Excess(p, self.usable_size(new_layout).1)) + .map(|p| Excess(p, usable_size.1)) } ``` From ede39d06f38f214f03d3178042147190e9c0a8db Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 17 Mar 2016 18:38:39 +0100 Subject: [PATCH 29/37] Updated the demo allocator implementation to reflect changes to the API. --- text/0000-kinds-of-allocators.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 885f6c505ea..5f1218da753 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -306,15 +306,15 @@ will expose: method. ```rust -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -enum BumpAllocError { Invalid, MemoryExhausted(alloc::Layout), Interference } +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum BumpAllocError { Invalid, MemoryExhausted(alloc::Layout), Interference } impl BumpAllocError { - fn is_transient(&self) { *self == BumpAllocError::Interference } + fn is_transient(&self) -> bool { *self == BumpAllocError::Interference } } impl alloc::AllocError for BumpAllocError { - fn invalid_input() -> Self { BumpAllocError::MemoryExhausted } + fn invalid_input() -> Self { BumpAllocError::Invalid } fn is_memory_exhausted(&self) -> bool { if let BumpAllocError::MemoryExhausted(_) = *self { true } else { false } } fn is_request_unsupported(&self) -> bool { false } } @@ -337,13 +337,14 @@ Here is the demo implementation of `Allocator` for the type. impl<'a> Allocator for &'a DumbBumpPool { type Error = BumpAllocError; - unsafe fn alloc(&mut self, layout: &alloc::Layout) -> Result { + unsafe fn alloc(&mut self, layout: alloc::Layout) -> Result { let curr = self.avail.load(Ordering::Relaxed) as usize; let align = *layout.align(); - let curr_aligned = (curr.overflowing_add(align - 1)) & !(align - 1); + let (sum, oflo) = curr.overflowing_add(align - 1); + let curr_aligned = sum & !(align - 1); let size = *layout.size(); let remaining = (self.end as usize) - curr_aligned; - if remaining <= size { + if oflo || remaining <= size { return Err(BumpAllocError::MemoryExhausted(layout.clone())); } @@ -359,7 +360,7 @@ impl<'a> Allocator for &'a DumbBumpPool { } } - unsafe fn dealloc(&mut self, _ptr: Address, _layout: &alloc::Layout) { + unsafe fn dealloc(&mut self, _ptr: Address, _layout: alloc::Layout) { // this bump-allocator just no-op's on dealloc } From ffdf71ee71e6bdbae22fbf2d8185be924b3fa71b Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 17 Mar 2016 19:05:30 +0100 Subject: [PATCH 30/37] Added missing `Sync` impl for `DumbBumpPool`, and also fixed some privacy oversights from earlier. The demo code is still not perfect (it currently presumes `Vec::new_in` addition, which is not great since another part of the RFC now says that standard library integration is specifically not addressed by this RFC). I'm working on revising the demo but I don't think that should hold up overall discussion. --- text/0000-kinds-of-allocators.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 5f1218da753..98d2feab31b 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -248,7 +248,7 @@ For this demo I want to try to minimize cleverness, so we will use ```rust impl DumbBumpPool { - fn new(name: &'static str, + pub fn new(name: &'static str, size_in_bytes: usize, start_align: usize) -> DumbBumpPool { unsafe { @@ -310,7 +310,7 @@ will expose: pub enum BumpAllocError { Invalid, MemoryExhausted(alloc::Layout), Interference } impl BumpAllocError { - fn is_transient(&self) -> bool { *self == BumpAllocError::Interference } + pub fn is_transient(&self) -> bool { *self == BumpAllocError::Interference } } impl alloc::AllocError for BumpAllocError { @@ -331,6 +331,20 @@ With that out of the way, here are some other design choices of note: (lifetime-scoped) threads, we will implement the `Allocator` interface as a *handle* pointing to the pool; in this case, a simple reference. + * Since the whole point of this particular bump-allocator is to + shared across threads (otherwise there would be no need to use + `AtomicPtr` for the `avail` field), we will want to implement the + (unsafe) `Sync` trait on it (doing this signals that it is safe to + send `&DumbBumpPool` to other threads). + +Here is that `impl Sync`. + +```rust +/// Note of course that this impl implies we must review all other +/// code for DumbBumpPool even more carefully. +unsafe impl Sync for DumbBumpPool { } +``` + Here is the demo implementation of `Allocator` for the type. ```rust From 9eae82ddbdc21969281801d93d31f559760a4710 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 18 Mar 2016 14:09:04 +0100 Subject: [PATCH 31/37] lifted the `fmt::Debug` bound from associated type up to `AllocError` trait itself. (The only place where `AllocError` is used is as a bound on that associated type, so does not present any additional burden on clients of `Allocator` itself, though it does rule out certain pathological programmtic constructions that I'm not worried about.) --- text/0000-kinds-of-allocators.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 98d2feab31b..5191b1c9ef8 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1724,7 +1724,7 @@ impl Layout { ```rust /// `AllocError` instances provide feedback about the cause of an allocation failure. -pub trait AllocError { +pub trait AllocError: fmt::Debug { /// Construct an error that indicates operation failure due to /// invalid input values for the request. /// @@ -1865,7 +1865,7 @@ pub unsafe trait Allocator { /// /// Many allocators will want to use the zero-sized /// `MemoryExhausted` type for this. - type Error: AllocError + fmt::Debug; + type Error: AllocError; ``` From 59ed824c1f906e655d239dd2472e9354806602ae Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 18 Mar 2016 14:15:26 +0100 Subject: [PATCH 32/37] Extended `AllocErr` enum variants with more contextual info about allocation failure cause. The important one: for `Unsupported` operations, lets have the allocator be able to provide more context about what was unsupported. (I chose `&'static str` here because that avoids allocation integration for the error message, but I would love to be convinced that we could employ `Cow<'static, str>` here instead, since that would be much more general purpose.) And since I was adding contextual information anyway, I decided to have the memory exhausted variant carry along the particular `Layout` straw that broke the camel's back. (Note that the `MemoryExhausted` *struct* still remains zero-sized.) --- text/0000-kinds-of-allocators.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 5191b1c9ef8..3700496ffa0 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1737,7 +1737,7 @@ pub trait AllocError: fmt::Debug { /// that would exhaust memory if arbitrary-precision arithmetic were /// used, clients are alternatively allowed to constuct an error /// representing memory exhaustion in this scenario.) - fn invalid_input() -> Self; + fn invalid_input(details: &'static str) -> Self where Self: Sized; /// Returns true if the error is due to hitting some resource /// limit, or otherwise running out of memory. This condition @@ -1800,32 +1800,38 @@ pub struct MemoryExhausted; /// Allocators that only support certain classes of inputs might choose this /// as their associated error type, so that clients can respond appropriately /// to specific error failure scenarios. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum AllocErr { /// Error due to hitting some resource limit or otherwise running /// out of memory. This condition strongly implies that *some* /// series of deallocations would allow a subsequent reissuing of /// the original allocation request to succeed. - Exhausted, + Exhausted { request: Layout }, /// Error due to allocator being fundamentally incapable of /// satisfying the original request. This condition implies that /// such an allocation request will never succeed on the given /// allocator, regardless of environment, memory pressure, or /// other contextual condtions. - Unsupported, + Unsupported { details: &'static str }, } impl AllocError for MemoryExhausted { - fn invalid_input() -> Self { MemoryExhausted } + fn invalid_input(_details: &'static str) -> Self { MemoryExhausted } fn is_memory_exhausted(&self) -> bool { true } fn is_request_unsupported(&self) -> bool { false } } impl AllocError for AllocErr { - fn invalid_input() -> Self { AllocErr::Unsupported } - fn is_memory_exhausted(&self) -> bool { *self == AllocErr::Exhausted } - fn is_request_unsupported(&self) -> bool { *self == AllocErr::Unsupported } + fn invalid_input(details: &'static str) -> Self { + AllocErr::Unsupported { details: details } + } + fn is_memory_exhausted(&self) -> bool { + if let AllocErr::Exhausted { .. } = *self { true } else { false } + } + fn is_request_unsupported(&self) -> bool { + if let AllocErr::Unsupported { .. } = *self { true } else { false } + } } ``` @@ -2078,7 +2084,7 @@ pub unsafe trait Allocator { } else { // (only occurs for zero-sized T) debug_assert!(mem::size_of::() == 0); - Err(Self::Error::invalid_input()) + Err(Self::Error::invalid_input("zero-sized type invalid for alloc_one")) } } @@ -2106,7 +2112,7 @@ pub unsafe trait Allocator { unsafe fn alloc_array(&mut self, n: usize) -> Result, Self::Error> { match Layout::array::(n) { Some(layout) => self.alloc(layout).map(|p|Unique::new(*p as *mut T)), - None => Err(Self::Error::invalid_input()), + None => Err(Self::Error::invalid_input("invalid layout for alloc_array")), } } @@ -2127,7 +2133,7 @@ pub unsafe trait Allocator { self.realloc(NonZero::new(ptr as *mut u8), k_old, k_new) .map(|p|Unique::new(*p as *mut T)) } else { - Err(Self::Error::invalid_input()) + Err(Self::Error::invalid_input("invalid layout for realloc_array")) } } @@ -2140,7 +2146,7 @@ pub unsafe trait Allocator { self.dealloc(raw_ptr, k); Ok(()) } else { - Err(Self::Error::invalid_input()) + Err(Self::Error::invalid_input("invalid layout for dealloc_array")) } } From fe9a9b254a49e1e1f0533fae52493cb182cc3122 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 18 Mar 2016 14:44:06 +0100 Subject: [PATCH 33/37] Added section on allocator trait objects. This includes the changes that were absolutely necessary to support them in the `Allocator` trait itself, as well as an (opinionated) type alias, `AllocatorObj`, for defining allocator trait objects. --- text/0000-kinds-of-allocators.md | 82 ++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 3700496ffa0..6b147287479 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -761,7 +761,7 @@ for example, `a.alloc_one::()` will return a `Unique` (or error). ## Unchecked variants -Finally, almost all of the methods above return `Result`, and guarantee some +Almost all of the methods above return `Result`, and guarantee some amount of input validation. (This is largely because I observed code duplication doing such validation on the client side; or worse, such validation accidentally missing.) @@ -787,6 +787,45 @@ of the preconditions hold. offered to impl's; but there is no guarantee that an arbitrary impl takes advantage of the privilege.) +## Object-oriented Allocators + +Finally, we get to object-oriented programming. + +Since the `Allocator` trait has an associated error type, one +cannot just encode virtually-dispatched allocator objects with +`Box` or `&Allocator`; trait objects need to have +their associated types specified as part of the object trait. + +In general, we expect allocator-parametric code to opt *not* to use +trait objects to generalize over allocators, but instead to use +generic types and instantiate those types with specific concrete +allocators. + +Nonetheless, it *is* an option to write `Box>`, or +`&Allocator`, when working with allocators that +use each corresponding error type. + + * (The allocator methods that are not object-safe, like + `fn alloc_one(&mut self)`, have a clause `where Self: Sized` to + ensure that their presence does not cause the `Allocator` trait as + a whole to become non-object-safe.) + +To encourage client code that chooses to use trait objects for their +allocators to try to standardize on one choice of associated `Error` +type, we provide a convenience `type` definition for +[allocator objects][], `AllocatorObj`, which makes an opinionated +decision about which one of the "standard error types" is the "right +one" for such general purpose objects: namely, `AllocErr`, since it is +both cheap to construct but also can provide some amount of +context-sensitive information about the original cause of an +allocation error. + +However, the main point remains that we expect this object-oriented +usage of allocators to be rare. If this assumption turns out to be +incorrect, we should revisit these decisions before stabilizing the +allocator API (that would be the time to e.g. remove the associated +error type). + ## Why this API [Why this API]: #why-this-api @@ -2078,7 +2117,8 @@ pub unsafe trait Allocator { /// `alloc`/`realloc` methods of this allocator. /// /// Returns `Err` for zero-sized `T`. - unsafe fn alloc_one(&mut self) -> Result, Self::Error> { + unsafe fn alloc_one(&mut self) -> Result, Self::Error> + where Self: Sized { if let Some(k) = Layout::new::() { self.alloc(k).map(|p|Unique::new(*p as *mut T)) } else { @@ -2096,7 +2136,8 @@ pub unsafe trait Allocator { /// undefined behavior. /// /// Captures a common usage pattern for allocators. - unsafe fn dealloc_one(&mut self, mut ptr: Unique) { + unsafe fn dealloc_one(&mut self, mut ptr: Unique) + where Self: Sized { let raw_ptr = NonZero::new(ptr.get_mut() as *mut T as *mut u8); self.dealloc(raw_ptr, Layout::new::().unwrap()); } @@ -2109,7 +2150,8 @@ pub unsafe trait Allocator { /// `alloc`/`realloc` methods of this allocator. /// /// Returns `Err` for zero-sized `T` or `n == 0`. - unsafe fn alloc_array(&mut self, n: usize) -> Result, Self::Error> { + unsafe fn alloc_array(&mut self, n: usize) -> Result, Self::Error> + where Self: Sized { match Layout::array::(n) { Some(layout) => self.alloc(layout).map(|p|Unique::new(*p as *mut T)), None => Err(Self::Error::invalid_input("invalid layout for alloc_array")), @@ -2127,7 +2169,8 @@ pub unsafe trait Allocator { unsafe fn realloc_array(&mut self, ptr: Unique, n_old: usize, - n_new: usize) -> Result, Self::Error> { + n_new: usize) -> Result, Self::Error> + where Self: Sized { let old_new_ptr = (Layout::array::(n_old), Layout::array::(n_new), *ptr); if let (Some(k_old), Some(k_new), ptr) = old_new_ptr { self.realloc(NonZero::new(ptr as *mut u8), k_old, k_new) @@ -2140,7 +2183,8 @@ pub unsafe trait Allocator { /// Deallocates a block suitable for holding `n` instances of `T`. /// /// Captures a common usage pattern for allocators. - unsafe fn dealloc_array(&mut self, ptr: Unique, n: usize) -> Result<(), Self::Error> { + unsafe fn dealloc_array(&mut self, ptr: Unique, n: usize) -> Result<(), Self::Error> + where Self: Sized { let raw_ptr = NonZero::new(*ptr as *mut u8); if let Some(k) = Layout::array::(n) { self.dealloc(raw_ptr, k); @@ -2232,7 +2276,8 @@ pub unsafe trait Allocator { /// Requires inputs are non-zero and do not cause arithmetic /// overflow, and `T` is not zero sized; otherwise yields /// undefined behavior. - unsafe fn alloc_array_unchecked(&mut self, n: usize) -> Option> { + unsafe fn alloc_array_unchecked(&mut self, n: usize) -> Option> + where Self: Sized { let layout = Layout::array_unchecked::(n); self.alloc_unchecked(layout).map(|p|Unique::new(*p as *mut T)) } @@ -2248,7 +2293,8 @@ pub unsafe trait Allocator { unsafe fn realloc_array_unchecked(&mut self, ptr: Unique, n_old: usize, - n_new: usize) -> Option> { + n_new: usize) -> Option> + where Self: Sized { let (k_old, k_new, ptr) = (Layout::array_unchecked::(n_old), Layout::array_unchecked::(n_new), *ptr); @@ -2263,9 +2309,27 @@ pub unsafe trait Allocator { /// Requires inputs are non-zero and do not cause arithmetic /// overflow, and `T` is not zero sized; otherwise yields /// undefined behavior. - unsafe fn dealloc_array_unchecked(&mut self, ptr: Unique, n: usize) { + unsafe fn dealloc_array_unchecked(&mut self, ptr: Unique, n: usize) + where Self: Sized { let layout = Layout::array_unchecked::(n); self.dealloc(NonZero::new(*ptr as *mut u8), layout); } } ``` + +### Allocator trait objects +[allocator objects]: #allocator-trait-objects + +```rust +/// `AllocatorObj` is a convenience for making allocator trait objects +/// such as `Box` or `&AllocatorObj`. (One cannot just +/// write `Box` because the one must specify the associated +/// error type as part of the trait object. +/// +/// Since one is pays the cost of virtual function dispatch when +/// calling methods on trait objects, this definition uses `AllocErr` +/// to encode more information when signalling errors in these +/// objects, rather than using the content-impoverished +/// `MemoryExhausted` error type for the associated error type. +pub type AllocatorObj = Allocator; +``` From 792eb3186e57261e605a63cda47b68e7177c8f8a Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 18 Mar 2016 14:46:08 +0100 Subject: [PATCH 34/37] bug fixes to `DumbBumpPool` example. --- text/0000-kinds-of-allocators.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 6b147287479..762b57d6121 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -249,8 +249,8 @@ For this demo I want to try to minimize cleverness, so we will use ```rust impl DumbBumpPool { pub fn new(name: &'static str, - size_in_bytes: usize, - start_align: usize) -> DumbBumpPool { + size_in_bytes: usize, + start_align: usize) -> DumbBumpPool { unsafe { let ptr = heap::allocate(size_in_bytes, start_align); if ptr.is_null() { panic!("allocation failed."); } @@ -307,14 +307,18 @@ will expose: ```rust #[derive(Clone, PartialEq, Eq, Debug)] -pub enum BumpAllocError { Invalid, MemoryExhausted(alloc::Layout), Interference } +pub enum BumpAllocError { + Invalid(&'static str), + MemoryExhausted(alloc::Layout), + Interference +} impl BumpAllocError { pub fn is_transient(&self) -> bool { *self == BumpAllocError::Interference } } impl alloc::AllocError for BumpAllocError { - fn invalid_input() -> Self { BumpAllocError::Invalid } + fn invalid_input(details: &'static str) -> Self { BumpAllocError::Invalid(details) } fn is_memory_exhausted(&self) -> bool { if let BumpAllocError::MemoryExhausted(_) = *self { true } else { false } } fn is_request_unsupported(&self) -> bool { false } } @@ -358,7 +362,7 @@ impl<'a> Allocator for &'a DumbBumpPool { let curr_aligned = sum & !(align - 1); let size = *layout.size(); let remaining = (self.end as usize) - curr_aligned; - if oflo || remaining <= size { + if oflo || remaining < size { return Err(BumpAllocError::MemoryExhausted(layout.clone())); } From 5a3abd22df2a2fa940a4c5f96d328e3e99fd9c10 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 1 Apr 2016 14:21:00 +0200 Subject: [PATCH 35/37] Most of the changes suggested by feedback during FCP period. * Removed associated `Error` type from `Allocator` trait; all methods now use `AllocErr` for error type. Removed `AllocError` trait and `MemoryExhausted` error. * Removed `fn max_size` and `fn max_align` methods; we can put them back later if someone demonstrates a need for them. * Added `fn realloc_in_place`. --- text/0000-kinds-of-allocators.md | 355 +++++++++---------------------- 1 file changed, 101 insertions(+), 254 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 762b57d6121..61b1b784c01 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -227,7 +227,8 @@ much we have allocated from the backing storage. sharing this demo allocator across scoped threads.) ```rust -struct DumbBumpPool { +#[derive(Debug)] +pub struct DumbBumpPool { name: &'static str, ptr: *mut u8, end: *mut u8, @@ -283,48 +284,7 @@ impl Drop for DumbBumpPool { } ``` -Now, before we get into the trait implementation itself, here is an -interesting simple design choice: - - * To show-off the error abstraction in the API, we make a special - error type that covers a third case that is not part of the - standard `enum AllocErr`. - -Specifically, our bump allocator has *three* error conditions that we -will expose: - - 1. the inputs could be invalid, - - 2. the memory could be exhausted, or, - - 3. there could be *interference* between two threads. - This latter scenario means that this allocator failed - on this memory request, but the client might - quite reasonably just *retry* the request. This is - an error condition specific to this allocator, so we - will identify it via a separate `fn is_transient` inherent - method. - -```rust -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum BumpAllocError { - Invalid(&'static str), - MemoryExhausted(alloc::Layout), - Interference -} - -impl BumpAllocError { - pub fn is_transient(&self) -> bool { *self == BumpAllocError::Interference } -} - -impl alloc::AllocError for BumpAllocError { - fn invalid_input(details: &'static str) -> Self { BumpAllocError::Invalid(details) } - fn is_memory_exhausted(&self) -> bool { if let BumpAllocError::MemoryExhausted(_) = *self { true } else { false } } - fn is_request_unsupported(&self) -> bool { false } -} -``` - -With that out of the way, here are some other design choices of note: +Here are some other design choices of note: * Our Bump Allocator is going to use a most simple-minded deallocation policy: calls to `fn dealloc` are no-ops. Instead, every request takes @@ -352,29 +312,31 @@ unsafe impl Sync for DumbBumpPool { } Here is the demo implementation of `Allocator` for the type. ```rust -impl<'a> Allocator for &'a DumbBumpPool { - type Error = BumpAllocError; - - unsafe fn alloc(&mut self, layout: alloc::Layout) -> Result { - let curr = self.avail.load(Ordering::Relaxed) as usize; +unsafe impl<'a> Allocator for &'a DumbBumpPool { + unsafe fn alloc(&mut self, layout: alloc::Layout) -> Result { let align = *layout.align(); - let (sum, oflo) = curr.overflowing_add(align - 1); - let curr_aligned = sum & !(align - 1); let size = *layout.size(); - let remaining = (self.end as usize) - curr_aligned; - if oflo || remaining < size { - return Err(BumpAllocError::MemoryExhausted(layout.clone())); - } - let curr = curr as *mut u8; - let curr_aligned = curr_aligned as *mut u8; - let new_curr = curr_aligned.offset(size as isize); + loop { + let curr = self.avail.load(Ordering::Relaxed) as usize; + let (sum, oflo) = curr.overflowing_add(align - 1); + let curr_aligned = sum & !(align - 1); + let remaining = (self.end as usize) - curr_aligned; + if oflo || remaining < size { + return Err(AllocErr::Exhausted { request: layout.clone() }); + } - if curr != self.avail.compare_and_swap(curr, new_curr, Ordering::Relaxed) { - return Err(BumpAllocError::Interference); - } else { - println!("alloc finis ok: 0x{:x} size: {}", curr_aligned as usize, size); - return Ok(NonZero::new(curr_aligned)); + let curr = curr as *mut u8; + let curr_aligned = curr_aligned as *mut u8; + let new_curr = curr_aligned.offset(size as isize); + + // If the allocation attempt hits interference ... + if curr != self.avail.compare_and_swap(curr, new_curr, Ordering::Relaxed) { + continue; // .. then try again + } else { + // println!("alloc finis ok: 0x{:x} size: {}", curr_aligned as usize, size); + return Ok(NonZero::new(curr_aligned)); + } } } @@ -382,8 +344,10 @@ impl<'a> Allocator for &'a DumbBumpPool { // this bump-allocator just no-op's on dealloc } - fn oom(&mut self, err: Self::Error) -> ! { - panic!("exhausted memory in {} on request {:?}", self.name, err); + fn oom(&mut self, err: AllocErr) -> ! { + let remaining = self.end as usize - self.avail.load(Ordering::Relaxed) as usize; + panic!("exhausted memory in {} on request {:?} with avail: {}; self: {:?}", + self.name, err, remaining, self); } } @@ -795,40 +759,18 @@ of the preconditions hold. Finally, we get to object-oriented programming. -Since the `Allocator` trait has an associated error type, one -cannot just encode virtually-dispatched allocator objects with -`Box` or `&Allocator`; trait objects need to have -their associated types specified as part of the object trait. - In general, we expect allocator-parametric code to opt *not* to use trait objects to generalize over allocators, but instead to use generic types and instantiate those types with specific concrete allocators. -Nonetheless, it *is* an option to write `Box>`, or -`&Allocator`, when working with allocators that -use each corresponding error type. +Nonetheless, it *is* an option to write `Box` or `&Allocator`. * (The allocator methods that are not object-safe, like `fn alloc_one(&mut self)`, have a clause `where Self: Sized` to ensure that their presence does not cause the `Allocator` trait as a whole to become non-object-safe.) -To encourage client code that chooses to use trait objects for their -allocators to try to standardize on one choice of associated `Error` -type, we provide a convenience `type` definition for -[allocator objects][], `AllocatorObj`, which makes an opinionated -decision about which one of the "standard error types" is the "right -one" for such general purpose objects: namely, `AllocErr`, since it is -both cheap to construct but also can provide some amount of -context-sensitive information about the original cause of an -allocation error. - -However, the main point remains that we expect this object-oriented -usage of allocators to be rare. If this assumption turns out to be -incorrect, we should revisit these decisions before stabilizing the -allocator API (that would be the time to e.g. remove the associated -error type). ## Why this API [Why this API]: #why-this-api @@ -883,21 +825,12 @@ My hypothesis is that the standard allocator API should embrace `Result` as the standard way for describing local error conditions in Rust. -In principle, we can use `Result` without adding *any* additional -overhead (at least in terms of the size of the values being returned -from the allocation calls), because the error type for the `Result` -can be zero-sized if so desired. That is why the error is an -associated type of the `Allocator`: allocators that want to ensure the -results have minimum size can use the zero-sized `MemoryExhausted` type -as their associated `Self::Error`. - - * `MemoryExhausted` is a specific error type meant for allocators - that could in principle handle *any* sane input request, if there - were sufficient memory available. (By "sane" we mean for example - that the input arguments do not cause an arithmetic overflow during - computation of the size of the memory block -- if they do, then it - is reasonable for an allocator with this error type to respond that - insufficent memory was available, rather than e.g. panicking.) + * A previous version of this RFC attempted to ensure that the use of + the `Result` type could avoid any additional overhead over a raw + pointer return value, by using a `NonZero` address type and a + zero-sized error type attached to the trait via an associated + `Error` type. But during the RFC process we decided that this + was not necessary. ### Why return `Result` rather than directly `oom` on failure @@ -1247,13 +1180,6 @@ few motivating examples that *are* clearly feasible and useful. `Address` an abuse of the `NonZero` type? (Or do we just need some constructor for `NonZero` that asserts that the input is non-zero)? - * Should we get rid of the `AllocError` bound entirely? Is the given set - of methods actually worth providing to all generic clients? - - (Keeping it seems very low cost to me; implementors can always opt - to use the `MemoryExhausted` error type, which is cheap. But my - intuition may be wrong.) - * Do we need `Allocator::max_size` and `Allocator::max_align` ? * Should default impl of `Allocator::max_align` return `None`, or is @@ -1287,6 +1213,14 @@ few motivating examples that *are* clearly feasible and useful. * Revised `fn oom` method to take the `Self::Error` as an input (so that the allocator can, indirectly, feed itself information about what went wrong). +* Removed associated `Error` type from `Allocator` trait; all methods now use `AllocErr` + for error type. Removed `AllocError` trait and `MemoryExhausted` error. + +* Removed `fn max_size` and `fn max_align` methods; we can put them back later if + someone demonstrates a need for them. + +* Added `fn realloc_in_place`. + # Appendices ## Bibliography @@ -1457,7 +1391,6 @@ sub-divided roughly accordingly to functionality. issue = "27700")] use core::cmp; -use core::fmt; use core::mem; use core::nonzero::NonZero; use core::ptr::{self, Unique}; @@ -1762,87 +1695,14 @@ impl Layout { ``` -### AllocError API -[error api]: #allocerror-api +### AllocErr API +[error api]: #allocerr-api ```rust -/// `AllocError` instances provide feedback about the cause of an allocation failure. -pub trait AllocError: fmt::Debug { - /// Construct an error that indicates operation failure due to - /// invalid input values for the request. - /// - /// This can be used, for example, to signal that allocation of - /// a zero-sized type was requested. - /// - /// As another example, it might be used to signal that an overflow - /// occurred during arithmetic computation with the input. (However, - /// since overflows can also occur during large allocation requests - /// that would exhaust memory if arbitrary-precision arithmetic were - /// used, clients are alternatively allowed to constuct an error - /// representing memory exhaustion in this scenario.) - fn invalid_input(details: &'static str) -> Self where Self: Sized; - - /// Returns true if the error is due to hitting some resource - /// limit, or otherwise running out of memory. This condition - /// serves as a hint that some series of deallocations *might* - /// allow a subsequent reissuing of the original allocation - /// request to succeed. - /// - /// Exhaustion is a common interpretation of an allocation failure; - /// e.g. usually when `malloc` returns `null`, it is because of - /// hitting a user resource limit or system memory exhaustion. - /// - /// Note that the resource exhaustion could be internal to the - /// original allocator (i.e. the only way to free up memory is by - /// deallocating memory attached to that allocator), or it could - /// be associated with some other state external to the original - /// allocator (e.g. freeing up memory or reducing fragmentation - /// globally might allow a call to the system `malloc` to succeed). - /// The `AllocError` trait does not distinguish between the two - /// scenarios (but instances of the associated `Allocator::Error` - /// type might provide ways to distinguish them). - /// - /// Finally, error responses to allocation input requests that are - /// *always* illegal for *any* allocator (e.g. zero-sized or - /// arithmetic-overflowing requests) are allowed to respond `true` - /// here. (This is to allow `MemoryExhausted` as a valid - /// zero-sized error type for an allocator that can handle all - /// "sane" requests.) - fn is_memory_exhausted(&self) -> bool; - - /// Returns true if the allocator is fundamentally incapable of - /// satisfying the original request. This condition implies that - /// such an allocation request would never succeed on *this* - /// allocator, regardless of environment, memory pressure, or - /// other contextual condtions. - /// - /// An example where this might arise: A block allocator that only - /// supports satisfying memory requests where each allocated block - /// is at most `K` bytes in size. - fn is_request_unsupported(&self) -> bool; -} - -/// The `MemoryExhausted` error represents a blanket condition -/// that the given request was not satisifed for some reason beyond -/// any particular limitations of a given allocator. -/// -/// It roughly corresponds to getting `null` back from a call to `malloc`: -/// you've probably exhausted memory (though there might be some other -/// explanation; see discussion with `AllocError::is_memory_exhausted`). -/// -/// Allocators that can in principle allocate any kind of legal input -/// might choose this as their associated error type. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct MemoryExhausted; - /// The `AllocErr` error specifies whether an allocation failure is /// specifically due to resource exhaustion or if it is due to /// something wrong when combining the given input arguments with this /// allocator. - -/// Allocators that only support certain classes of inputs might choose this -/// as their associated error type, so that clients can respond appropriately -/// to specific error failure scenarios. #[derive(Clone, PartialEq, Eq, Debug)] pub enum AllocErr { /// Error due to hitting some resource limit or otherwise running @@ -1859,24 +1719,23 @@ pub enum AllocErr { Unsupported { details: &'static str }, } -impl AllocError for MemoryExhausted { - fn invalid_input(_details: &'static str) -> Self { MemoryExhausted } - fn is_memory_exhausted(&self) -> bool { true } - fn is_request_unsupported(&self) -> bool { false } -} - -impl AllocError for AllocErr { - fn invalid_input(details: &'static str) -> Self { +impl AllocErr { + pub fn invalid_input(details: &'static str) -> Self { AllocErr::Unsupported { details: details } } - fn is_memory_exhausted(&self) -> bool { + pub fn is_memory_exhausted(&self) -> bool { if let AllocErr::Exhausted { .. } = *self { true } else { false } } - fn is_request_unsupported(&self) -> bool { + pub fn is_request_unsupported(&self) -> bool { if let AllocErr::Unsupported { .. } = *self { true } else { false } } } +/// The `CannotReallocInPlace` error is used when `fn realloc_in_place` +/// was unable to reuse the given memory block for a requested layout. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct CannotReallocInPlace; + ``` ### Allocator trait header @@ -1909,12 +1768,6 @@ impl AllocError for AllocErr { /// `usable_size`. /// pub unsafe trait Allocator { - /// When allocation requests cannot be satisified, an instance of - /// this error is returned. - /// - /// Many allocators will want to use the zero-sized - /// `MemoryExhausted` type for this. - type Error: AllocError; ``` @@ -1937,7 +1790,7 @@ pub unsafe trait Allocator { /// 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; + unsafe fn alloc(&mut self, layout: Layout) -> Result; /// Deallocate the memory referenced by `ptr`. /// @@ -1959,7 +1812,7 @@ pub unsafe trait Allocator { /// instead they should return an appropriate error from the /// invoked method, and let the client decide whether to invoke /// this `oom` method. - fn oom(&mut self, _: Self::Error) -> ! { + fn oom(&mut self, _: AllocErr) -> ! { unsafe { ::core::intrinsics::abort() } } ``` @@ -1969,24 +1822,7 @@ pub unsafe trait Allocator { ```rust // == ALLOCATOR-SPECIFIC QUANTITIES AND LIMITS == - // max_size, max_align, usable_size - - /// The maximum requestable size in bytes for memory blocks - /// managed by this allocator. - /// - /// Returns `None` if this allocator has no explicit maximum size. - /// (Note that such allocators may well still have an *implicit* - /// maximum size; i.e. allocation requests can always fail.) - fn max_size(&self) -> Option { None } - - /// The maximum requestable alignment in bytes for memory blocks - /// managed by this allocator. - /// - /// Returns `None` if this allocator has no assigned maximum - /// alignment. (Note that such allocators may well still have an - /// *implicit* maximum alignment; i.e. allocation requests can - /// always fail.) - fn max_align(&self) -> Option { None } + // usable_size /// Returns bounds on the guaranteed usable size of a successful /// allocation created with the specified `layout`. @@ -2037,10 +1873,9 @@ pub unsafe trait Allocator { /// /// Behavior undefined if either of latter two constraints are unmet. /// - /// In addition, `new_layout` should not impose a stronger alignment + /// In addition, `new_layout` should not impose a different alignment /// constraint than `layout`. (In other words, `new_layout.align()` - /// must evenly divide `layout.align()`; note this implies the - /// alignment of `new_layout` must not exceed that of `layout`.) + /// should equal `layout.align()`.) /// However, behavior is well-defined (though underspecified) when /// this constraint is violated; further discussion below. /// @@ -2056,8 +1891,9 @@ pub unsafe trait Allocator { /// alignment of `layout`, or if reallocation otherwise fails. (Note /// that did not say "if and only if" -- in particular, an /// implementation of this method *can* return `Ok` if - /// `new_layout.align() > old_layout.align()`; or it can return `Err` - /// in that scenario.) + /// `new_layout.align() != old_layout.align()`; or it can return `Err` + /// in that scenario, depending on whether this allocator + /// can dynamically adjust the alignment constraint for the block.) /// /// If this method returns `Err`, then ownership of the memory /// block has not been transferred to this allocator, and the @@ -2065,7 +1901,7 @@ pub unsafe trait Allocator { unsafe fn realloc(&mut self, ptr: Address, layout: Layout, - new_layout: Layout) -> Result { + new_layout: Layout) -> Result { let (min, max) = self.usable_size(&layout); let s = new_layout.size(); // All Layout alignments are powers of two, so a comparison @@ -2087,7 +1923,7 @@ pub unsafe trait Allocator { /// Behaves like `fn alloc`, but also returns the whole size of /// the returned block. For some `layout` inputs, like arrays, this /// may include extra storage usable for additional data. - unsafe fn alloc_excess(&mut self, layout: Layout) -> Result { + unsafe fn alloc_excess(&mut self, layout: Layout) -> Result { let usable_size = self.usable_size(&layout); self.alloc(layout).map(|p| Excess(p, usable_size.1)) } @@ -2098,12 +1934,40 @@ pub unsafe trait Allocator { unsafe fn realloc_excess(&mut self, ptr: Address, layout: Layout, - new_layout: Layout) -> Result { + new_layout: Layout) -> Result { let usable_size = self.usable_size(&new_layout); self.realloc(ptr, layout, new_layout) .map(|p| Excess(p, usable_size.1)) } + /// Attempts to extend the allocation referenced by `ptr` to fit `new_layout`. + /// + /// * `ptr` must have previously been provided via this allocator. + /// + /// * `layout` must *fit* the `ptr` (see above). (The `new_layout` + /// argument need not fit it.) + /// + /// Behavior undefined if either of latter two constraints are unmet. + /// + /// If this returns `Ok`, then the allocator has asserted that the + /// memory block referenced by `ptr` now fits `new_layout`, and thus can + /// be used to carry data of that layout. (The allocator is allowed to + /// expend effort to accomplish this, such as extending the memory block to + /// include successor blocks, or virtual memory tricks.) + /// + /// If this returns `Err`, then the allocator has made no assertion + /// about whether the memory block referenced by `ptr` can or cannot + /// fit `new_layout`. + /// + /// In either case, ownership of the memory block referenced by `ptr` + /// has not been transferred, and the contents of the memory block + /// are unaltered. + unsafe fn realloc_in_place(&mut self, + ptr: Address, + layout: Layout, + new_layout: Layout) -> Result<(), CannotReallocInPlace> { + Err(CannotReallocInPlace) + } ``` ### Allocator convenience methods for common usage patterns @@ -2121,14 +1985,14 @@ pub unsafe trait Allocator { /// `alloc`/`realloc` methods of this allocator. /// /// Returns `Err` for zero-sized `T`. - unsafe fn alloc_one(&mut self) -> Result, Self::Error> + unsafe fn alloc_one(&mut self) -> Result, AllocErr> where Self: Sized { if let Some(k) = Layout::new::() { self.alloc(k).map(|p|Unique::new(*p as *mut T)) } else { // (only occurs for zero-sized T) debug_assert!(mem::size_of::() == 0); - Err(Self::Error::invalid_input("zero-sized type invalid for alloc_one")) + Err(AllocErr::invalid_input("zero-sized type invalid for alloc_one")) } } @@ -2154,11 +2018,11 @@ pub unsafe trait Allocator { /// `alloc`/`realloc` methods of this allocator. /// /// Returns `Err` for zero-sized `T` or `n == 0`. - unsafe fn alloc_array(&mut self, n: usize) -> Result, Self::Error> + unsafe fn alloc_array(&mut self, n: usize) -> Result, AllocErr> where Self: Sized { match Layout::array::(n) { Some(layout) => self.alloc(layout).map(|p|Unique::new(*p as *mut T)), - None => Err(Self::Error::invalid_input("invalid layout for alloc_array")), + None => Err(AllocErr::invalid_input("invalid layout for alloc_array")), } } @@ -2173,28 +2037,28 @@ pub unsafe trait Allocator { unsafe fn realloc_array(&mut self, ptr: Unique, n_old: usize, - n_new: usize) -> Result, Self::Error> + n_new: usize) -> Result, AllocErr> where Self: Sized { let old_new_ptr = (Layout::array::(n_old), Layout::array::(n_new), *ptr); if let (Some(k_old), Some(k_new), ptr) = old_new_ptr { self.realloc(NonZero::new(ptr as *mut u8), k_old, k_new) .map(|p|Unique::new(*p as *mut T)) } else { - Err(Self::Error::invalid_input("invalid layout for realloc_array")) + Err(AllocErr::invalid_input("invalid layout for realloc_array")) } } /// Deallocates a block suitable for holding `n` instances of `T`. /// /// Captures a common usage pattern for allocators. - unsafe fn dealloc_array(&mut self, ptr: Unique, n: usize) -> Result<(), Self::Error> + unsafe fn dealloc_array(&mut self, ptr: Unique, n: usize) -> Result<(), AllocErr> where Self: Sized { let raw_ptr = NonZero::new(*ptr as *mut u8); if let Some(k) = Layout::array::(n) { self.dealloc(raw_ptr, k); Ok(()) } else { - Err(Self::Error::invalid_input("invalid layout for dealloc_array")) + Err(AllocErr::invalid_input("invalid layout for dealloc_array")) } } @@ -2320,20 +2184,3 @@ pub unsafe trait Allocator { } } ``` - -### Allocator trait objects -[allocator objects]: #allocator-trait-objects - -```rust -/// `AllocatorObj` is a convenience for making allocator trait objects -/// such as `Box` or `&AllocatorObj`. (One cannot just -/// write `Box` because the one must specify the associated -/// error type as part of the trait object. -/// -/// Since one is pays the cost of virtual function dispatch when -/// calling methods on trait objects, this definition uses `AllocErr` -/// to encode more information when signalling errors in these -/// objects, rather than using the content-impoverished -/// `MemoryExhausted` error type for the associated error type. -pub type AllocatorObj = Allocator; -``` From 7c2c4446129d1874e6d86a3125b31bcc8821e271 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 1 Apr 2016 15:25:28 +0200 Subject: [PATCH 36/37] Allow `Layout` to represent zero-sized layouts. Removed uses of `NonZero`. Revised specifications of (hopefully all) relevant methods to indicate that they may or may not support allocation of zero-sized layouts (but they should return an appropriate `Err` when given such). (Now, the requirement to return an `Err` does imply a branch that arguably we would like to avoid. It would be good to double-check this, and potentially try to inline the initial checks into the call site.) --- text/0000-kinds-of-allocators.md | 166 ++++++++++++++++--------------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 61b1b784c01..4146f6ceb2c 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -69,8 +69,8 @@ following: ## Allocators should feel "rustic" In addition, for Rust we want an allocator API design that leverages -the core type machinery and language idioms (e.g. using `Result`, with -a `NonZero` okay variant and a zero-sized error variant), and provides +the core type machinery and language idioms (e.g. using `Result` to +propagate dynamic error conditions), and provides premade functions for common patterns for allocator clients (such as allocating either single instances of a type, or arrays of some types of dynamically-determined length). @@ -314,11 +314,12 @@ Here is the demo implementation of `Allocator` for the type. ```rust unsafe impl<'a> Allocator for &'a DumbBumpPool { unsafe fn alloc(&mut self, layout: alloc::Layout) -> Result { - let align = *layout.align(); - let size = *layout.size(); + let align = layout.align(); + let size = layout.size(); + let mut curr_addr = self.avail.load(Ordering::Relaxed); loop { - let curr = self.avail.load(Ordering::Relaxed) as usize; + let curr = curr_addr as usize; let (sum, oflo) = curr.overflowing_add(align - 1); let curr_aligned = sum & !(align - 1); let remaining = (self.end as usize) - curr_aligned; @@ -326,16 +327,17 @@ unsafe impl<'a> Allocator for &'a DumbBumpPool { return Err(AllocErr::Exhausted { request: layout.clone() }); } - let curr = curr as *mut u8; let curr_aligned = curr_aligned as *mut u8; let new_curr = curr_aligned.offset(size as isize); + let attempt = self.avail.compare_and_swap(curr_addr, new_curr, Ordering::Relaxed); // If the allocation attempt hits interference ... - if curr != self.avail.compare_and_swap(curr, new_curr, Ordering::Relaxed) { + if curr_addr != attempt { + curr_addr = attempt; continue; // .. then try again } else { - // println!("alloc finis ok: 0x{:x} size: {}", curr_aligned as usize, size); - return Ok(NonZero::new(curr_aligned)); + println!("alloc finis ok: 0x{:x} size: {}", curr_aligned as usize, size); + return Ok(curr_aligned); } } } @@ -674,11 +676,6 @@ worth hard-coding into the method signatures. * Therefore, I made [type aliases][] for `Size`, `Capacity`, `Alignment`, and `Address`. -Furthermore, all values of the above types must be non-zero for any -allocation action to make sense. - - * Therefore, I made them instances of the `NonZero` type. - ### Basic implementation An instance of an allocator has many methods, but an implementor of @@ -1221,6 +1218,9 @@ few motivating examples that *are* clearly feasible and useful. * Added `fn realloc_in_place`. +* Removed uses of `NonZero`. Made `Layout` able to represent zero-sized layouts. + A given `Allocator` may or may not support zero-sized layouts. + # Appendices ## Bibliography @@ -1401,11 +1401,11 @@ use core::ptr::{self, Unique}; [type aliases]: #type-aliases ```rust -pub type Size = NonZero; -pub type Capacity = NonZero; -pub type Alignment = NonZero; +pub type Size = usize; +pub type Capacity = usize; +pub type Alignment = usize; -pub type Address = NonZero<*mut u8>; +pub type Address = *mut u8; /// Represents the combination of a starting address and /// a total capacity of the returned block. @@ -1426,8 +1426,7 @@ fn size_align() -> (usize, usize) { /// An instance of `Layout` describes a particular layout of memory. /// You build a `Layout` up as an input to give to an allocator. /// -/// All layouts have an associated positive size; note that this implies -/// zero-sized types have no corresponding layout. +/// All layouts have an associated non-negative size and positive alignment. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Layout { // size of the requested block of memory, measured in bytes. @@ -1450,38 +1449,29 @@ pub struct Layout { impl Layout { // (private constructor) fn from_size_align(size: usize, align: usize) -> Layout { - assert!(align.is_power_of_two()); - let size = unsafe { assert!(size > 0); NonZero::new(size) }; - let align = unsafe { assert!(align > 0); NonZero::new(align) }; + assert!(align.is_power_of_two()); + assert!(align > 0); Layout { size: size, align: align } } /// The minimum size in bytes for a memory block of this layout. - pub fn size(&self) -> NonZero { self.size } + pub fn size(&self) -> usize { self.size } /// The minimum byte alignment for a memory block of this layout. - pub fn align(&self) -> NonZero { self.align } + pub fn align(&self) -> usize { self.align } /// Constructs a `Layout` suitable for holding a value of type `T`. - /// Returns `None` if no such layout exists (e.g. for zero-sized `T`). - pub fn new() -> Option { + pub fn new() -> Self { let (size, align) = size_align::(); - if size > 0 { Some(Layout::from_size_align(size, align)) } else { None } + Layout::from_size_align(size, align) } /// Produces layout describing a record that could be used to /// allocate backing structure for `T` (which could be a trait /// or other unsized type like a slice). - /// - /// Returns `None` when no such layout exists; for example, when `x` - /// is a reference to a zero-sized type. - pub fn for_value(t: &T) -> Option { + pub fn for_value(t: &T) -> Self { let (size, align) = (mem::size_of_val(t), mem::align_of_val(t)); - if size > 0 { - Some(Layout::from_size_align(size, align)) - } else { - None - } + Layout::from_size_align(size, align) } /// Creates a layout describing the record that can hold a value @@ -1499,8 +1489,8 @@ impl Layout { if align > self.align { let pow2_align = align.checked_next_power_of_two().unwrap(); debug_assert!(pow2_align > 0); // (this follows from self.align > 0...) - Layout { align: unsafe { NonZero::new(pow2_align) }, - ..*self } + Layout { align: pow2_align, + ..*self } } else { self.clone() } @@ -1518,9 +1508,9 @@ impl Layout { /// whole record, because `self.align` would not provide /// sufficient constraint. 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); + debug_assert!(align <= self.align()); + let len = self.size(); + let len_rounded_up = (len + align - 1) & !(align - 1); return len_rounded_up - len; } @@ -1531,9 +1521,8 @@ impl Layout { /// layout of the array and `offs` is the distance between the start /// of each element in the array. /// - /// On zero `n` or arithmetic overflow, returns `None`. + /// On arithmetic overflow, returns `None`. pub fn repeat(&self, n: usize) -> Option<(Self, usize)> { - if n == 0 { return None; } let padded_size = match self.size.checked_add(self.padding_needed_for(self.align)) { None => return None, Some(padded_size) => padded_size, @@ -1542,7 +1531,7 @@ impl Layout { None => return None, Some(alloc_size) => alloc_size, }; - Some((Layout::from_size_align(alloc_size, *self.align), padded_size)) + Some((Layout::from_size_align(alloc_size, self.align), padded_size)) } /// Creates a layout describing the record for `self` followed by @@ -1557,24 +1546,24 @@ impl Layout { /// /// On arithmetic overflow, returns `None`. pub fn extend(&self, next: Self) -> Option<(Self, usize)> { - let new_align = unsafe { NonZero::new(cmp::max(*self.align, *next.align)) }; + 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; - let new_size = offset + *next.size(); - Some((Layout::from_size_align(new_size, *new_align), offset)) + let offset = self.size() + pad; + let new_size = offset + next.size(); + Some((Layout::from_size_align(new_size, new_align), offset)) } /// Creates a layout describing the record for `n` instances of /// `self`, with no padding between each instance. /// - /// On zero `n` or overflow, returns `None`. + /// On arithmetic overflow, returns `None`. pub fn repeat_packed(&self, n: usize) -> Option { let scaled = match self.size().checked_mul(n) { None => return None, Some(scaled) => scaled, }; - let size = unsafe { assert!(scaled > 0); NonZero::new(scaled) }; + let size = { assert!(scaled > 0); scaled }; Some(Layout { size: size, align: self.align }) } @@ -1594,12 +1583,11 @@ impl Layout { /// /// On arithmetic overflow, returns `None`. pub fn extend_packed(&self, next: Self) -> Option<(Self, usize)> { - let new_size = match self.size().checked_add(*next.size()) { + let new_size = match self.size().checked_add(next.size()) { None => return None, Some(new_size) => new_size, }; - let new_size = unsafe { NonZero::new(new_size) }; - Some((Layout { size: new_size, ..*self }, *self.size())) + Some((Layout { size: new_size, ..*self }, self.size())) } // Below family of methods *assume* inputs are pre- or @@ -1611,7 +1599,6 @@ impl Layout { // methods are `unsafe`. /// Creates layout describing the record for a single instance of `T`. - /// Requires `T` has non-zero size. pub unsafe fn new_unchecked() -> Self { let (size, align) = size_align::(); Layout::from_size_align(size, align) @@ -1676,7 +1663,7 @@ impl Layout { /// On zero `n`, zero-sized `T`, or arithmetic overflow, returns `None`. pub fn array(n: usize) -> Option { Layout::new::() - .and_then(|k| k.repeat(n)) + .repeat(n) .map(|(k, offs)| { debug_assert!(offs == mem::size_of::()); k @@ -1716,6 +1703,9 @@ pub enum AllocErr { /// such an allocation request will never succeed on the given /// allocator, regardless of environment, memory pressure, or /// other contextual condtions. + /// + /// For example, an allocator that does not support zero-sized + /// blocks can return this error variant. Unsupported { details: &'static str }, } @@ -1913,7 +1903,7 @@ pub unsafe trait Allocator { 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)); + ptr::copy(ptr as *const u8, new_ptr, cmp::min(old_size, new_size)); self.dealloc(ptr, layout); } result @@ -1966,6 +1956,7 @@ pub unsafe trait Allocator { ptr: Address, layout: Layout, new_layout: Layout) -> Result<(), CannotReallocInPlace> { + let (_, _, _) = (ptr, layout, new_layout); Err(CannotReallocInPlace) } ``` @@ -1984,14 +1975,13 @@ pub unsafe trait Allocator { /// The returned block is suitable for passing to the /// `alloc`/`realloc` methods of this allocator. /// - /// Returns `Err` for zero-sized `T`. + /// May return `Err` for zero-sized `T`. unsafe fn alloc_one(&mut self) -> Result, AllocErr> where Self: Sized { - if let Some(k) = Layout::new::() { + let k = Layout::new::(); + if k.size() > 0 { self.alloc(k).map(|p|Unique::new(*p as *mut T)) } else { - // (only occurs for zero-sized T) - debug_assert!(mem::size_of::() == 0); Err(AllocErr::invalid_input("zero-sized type invalid for alloc_one")) } } @@ -2006,8 +1996,8 @@ pub unsafe trait Allocator { /// Captures a common usage pattern for allocators. unsafe fn dealloc_one(&mut self, mut ptr: Unique) where Self: Sized { - let raw_ptr = NonZero::new(ptr.get_mut() as *mut T as *mut u8); - self.dealloc(raw_ptr, Layout::new::().unwrap()); + let raw_ptr = ptr.get_mut() as *mut T as *mut u8; + self.dealloc(raw_ptr, Layout::new::()); } /// Allocates a block suitable for holding `n` instances of `T`. @@ -2017,12 +2007,20 @@ pub unsafe trait Allocator { /// The returned block is suitable for passing to the /// `alloc`/`realloc` methods of this allocator. /// - /// Returns `Err` for zero-sized `T` or `n == 0`. + /// May return `Err` for zero-sized `T` or `n == 0`. + /// + /// Always returns `Err` on arithmetic overflow. unsafe fn alloc_array(&mut self, n: usize) -> Result, AllocErr> where Self: Sized { match Layout::array::(n) { - Some(layout) => self.alloc(layout).map(|p|Unique::new(*p as *mut T)), - None => Err(AllocErr::invalid_input("invalid layout for alloc_array")), + Some(ref layout) if layout.size() > 0 => { + self.alloc(layout.clone()) + .map(|p| { + println!("alloc_array layout: {:?} yielded p: {:?}", layout, p); + Unique::new(p as *mut T) + }) + } + _ => Err(AllocErr::invalid_input("invalid layout for alloc_array")), } } @@ -2034,17 +2032,23 @@ pub unsafe trait Allocator { /// /// The returned block is suitable for passing to the /// `alloc`/`realloc` methods of this allocator. + /// + /// May return `Err` for zero-sized `T` or `n == 0`. + /// + /// Always returns `Err` on arithmetic overflow. unsafe fn realloc_array(&mut self, ptr: Unique, n_old: usize, n_new: usize) -> Result, AllocErr> where Self: Sized { - let old_new_ptr = (Layout::array::(n_old), Layout::array::(n_new), *ptr); - if let (Some(k_old), Some(k_new), ptr) = old_new_ptr { - self.realloc(NonZero::new(ptr as *mut u8), k_old, k_new) - .map(|p|Unique::new(*p as *mut T)) - } else { - Err(AllocErr::invalid_input("invalid layout for realloc_array")) + match (Layout::array::(n_old), Layout::array::(n_new), *ptr) { + (Some(ref k_old), Some(ref k_new), ptr) if k_old.size() > 0 && k_new.size() > 0 => { + self.realloc(ptr as *mut u8, k_old.clone(), k_new.clone()) + .map(|p|Unique::new(p as *mut T)) + } + _ => { + Err(AllocErr::invalid_input("invalid layout for realloc_array")) + } } } @@ -2053,12 +2057,14 @@ pub unsafe trait Allocator { /// Captures a common usage pattern for allocators. unsafe fn dealloc_array(&mut self, ptr: Unique, n: usize) -> Result<(), AllocErr> where Self: Sized { - let raw_ptr = NonZero::new(*ptr as *mut u8); - if let Some(k) = Layout::array::(n) { - self.dealloc(raw_ptr, k); - Ok(()) - } else { - Err(AllocErr::invalid_input("invalid layout for dealloc_array")) + let raw_ptr = *ptr as *mut u8; + match Layout::array::(n) { + Some(ref k) if k.size() > 0 => { + Ok(self.dealloc(raw_ptr, k.clone())) + } + _ => { + Err(AllocErr::invalid_input("invalid layout for dealloc_array")) + } } } @@ -2166,7 +2172,7 @@ pub unsafe trait Allocator { let (k_old, k_new, ptr) = (Layout::array_unchecked::(n_old), Layout::array_unchecked::(n_new), *ptr); - self.realloc_unchecked(NonZero::new(ptr as *mut u8), k_old, k_new) + self.realloc_unchecked(ptr as *mut u8, k_old, k_new) .map(|p|Unique::new(*p as *mut T)) } @@ -2180,7 +2186,7 @@ pub unsafe trait Allocator { unsafe fn dealloc_array_unchecked(&mut self, ptr: Unique, n: usize) where Self: Sized { let layout = Layout::array_unchecked::(n); - self.dealloc(NonZero::new(*ptr as *mut u8), layout); + self.dealloc(*ptr as *mut u8, layout); } } ``` From 117e5fca0988a1b0c4b6e4de83b633f92b0c9c84 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 1 Apr 2016 15:57:47 +0200 Subject: [PATCH 37/37] Add mention of where `fn oom` should go. --- text/0000-kinds-of-allocators.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-kinds-of-allocators.md b/text/0000-kinds-of-allocators.md index 4146f6ceb2c..441ecf761e3 100644 --- a/text/0000-kinds-of-allocators.md +++ b/text/0000-kinds-of-allocators.md @@ -1162,6 +1162,12 @@ few motivating examples that *are* clearly feasible and useful. over the underlying system allocator, while the convenience methods would truly be convenient.) + * Should `oom` be a free-function rather than a method on `Allocator`? + (The reason I want it on `Allocator` is so that it can provide feedback + about the allocator's state at the time of the OOM. Zoxc has argued + on the RFC thread that some forms of static analysis, to prove `oom` is + never invoked, would prefer it to be a free function.) + # Unresolved questions [unresolved]: #unresolved-questions