Skip to content

Commit 770abea

Browse files
committed
Use a trait instead of free functions
1 parent 56d6820 commit 770abea

File tree

1 file changed

+101
-123
lines changed

1 file changed

+101
-123
lines changed

Diff for: text/0000-global-allocators.md

+101-123
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ There are a couple of issues with the current approach:
3030

3131
A C-style ABI is error prone - nothing ensures that the signatures are correct,
3232
and if a function is omitted that error will be caught by the linker rather than
33-
compiler. The Macros 1.1 API is similar in that certain special functions must
34-
be identified to the compiler, and in that case a special attribute
35-
(`#[proc_macro_derive]`)is used rather than a magic symbol name.
33+
compiler.
34+
35+
Allocators have some state, and with the current API, that state is forced to be
36+
truly global since bare functions can't carry state.
3637

3738
Since an allocator is automatically selected when it is pulled into the crate
3839
graph, it is painful to compose allocators. For example, one may want to create
@@ -65,143 +66,128 @@ pinning, and diagnostic output dumps for code that depends on jemalloc directly.
6566

6667
## Defining an allocator
6768

68-
An allocator crate identifies itself as such by applying the `#![allocator]`
69-
annotate at the crate root. It then defines a specific set of functions which
70-
are tagged with attributes:
69+
We introduce a new trait, `GlobalAllocator`. It is similar to the `Allocator`
70+
trait described in [RFC 1398][], but is stripped down and the methods take
71+
`&self` rather than `&mut self`.
72+
73+
[RFC 1398]: https://github.com/rust-lang/rfcs/blob/master/text/1398-kinds-of-allocators.md
7174

7275
```rust
73-
#![allocator]
74-
75-
/// Returns a pointer to `size` bytes of memory aligned to `align`.
76-
///
77-
/// On failure, returns a null pointer.
78-
///
79-
/// Behavior is undefined if the requested size is 0 or the alignment is not a
80-
/// power of 2. The alignment must be no larger than the largest supported page
81-
/// size on the platform.
82-
///
83-
/// This function is required.
84-
#[allocator(allocate)]
85-
pub fn allocate(size: usize, align: usize) -> *mut u8 {
86-
...
76+
77+
#[lang = "global_allocator"]
78+
pub unsafe trait GlobalAllocator: Send + Sync {
79+
/// Returns a pointer to a newly allocated region of memory suitable for the
80+
/// provided `Layout`. The contents of the memory are undefined.
81+
///
82+
/// On failure, returns a null pointer.
83+
pub fn allocate(&self, layout: Layout) -> *mut u8;
84+
85+
/// Returns a pointer to a newly allocated region of memory suitable for the
86+
/// provided `Layout`. The memory is guaranteed to contain zeroes.
87+
///
88+
/// On failure, returns a null pointer.
89+
pub fn allocate_zeroed(&self, layout: Layout) -> *mut u8 {
90+
let ptr = self.allocate(layout);
91+
if !ptr.is_null() {
92+
ptr::write_bytes(ptr, 0, layout.size());
93+
}
94+
ptr
95+
}
96+
97+
/// Deallocates the memory referenced by `ptr`.
98+
///
99+
/// The pointer must correspond to a region of memory previously allocated
100+
/// by this allocator with the provided layout.
101+
pub unsafe fn deallocate(&self, ptr: *mut u8, layout: Layout);
102+
103+
/// Resizes the allocation referenced by `ptr` a new layout.
104+
///
105+
/// On failure, returns a null pointer and leaves the original allocation
106+
/// intact.
107+
///
108+
/// If the allocation was relocated, the memory at the passed-in pointer is
109+
/// undefined after the call.
110+
///
111+
/// The pointer must correspond to a region of memory previously allocated
112+
/// by this allocator with the provided layout.
113+
pub fn reallocate(&self, ptr: *mut u8, old_layout: Layout, layout: Layout) -> *mut u8 {
114+
let new_ptr = self.alloc(layout);
115+
if !new_ptr.is_null() {
116+
ptr::copy_nonoverlapping(ptr, new_ptr, cmp::min(old_layout.size(), layout.size()));
117+
self.deallocate(ptr);
118+
}
119+
new_ptr
120+
}
87121
}
122+
```
123+
124+
Two methods currently defined in the global allocatr API are not present on this
125+
trait: `usable_size` which is used nowhere in the standard library, and
126+
`reallocate_inplace`, which is only used in libarena.
88127

89-
/// Returns a pointer to `size` bytes of memory aligned to `align`, and
90-
/// initialized with zeroes.
91-
///
92-
/// On failure, returns a null pointer.
93-
///
94-
/// Behavior is undefined if the requested size is 0 or the alignment is not a
95-
/// power of 2. The alignment must be no larger than the largest supported page
96-
/// size on the platform.
97-
///
98-
/// This function is optional. If not provided, `allocate` will be called and
99-
/// the resulting buffer will be zerored.
100-
#[allocator(allocate_zeroed)]
101-
pub fn allocate_zeroed(size: usize, align: usize) -> *mut u8 {
128+
A global allocator is a type implementing `GlobalAllocator` which can be
129+
constructed in a constant expression.
130+
131+
## Using an allocator
132+
133+
While the `GlobalAllocator` trait can be used like any other, the most common
134+
usage of a global allocator is through the functions defined in the
135+
`std::heap` module. It contains free functions corresponding to each of the
136+
methods defined on the `GlobalAllocator` trait:
137+
138+
```rust
139+
pub fn allocate(layout: Layout) -> *mut u8 {
102140
...
103141
}
104142

105-
/// Deallocates the memory referenced by `ptr`.
106-
///
107-
/// The `ptr` parameter must not be null.
108-
///
109-
/// The `old_size` and `align` parameters are the parameters that were used to
110-
/// create the allocation referenced by `ptr`.
111-
///
112-
/// This function is required.
113-
#[allocator(deallocate)]
114-
pub fn deallocate(ptr: *mut u8, old_size: usize, align: usize) {
143+
pub fn allocate_zeroed(layout: Layout) -> *mut u8 {
115144
...
116145
}
117146

118-
/// Resizes the allocation referenced by `ptr` to `size` bytes.
119-
///
120-
/// On failure, returns a null pointer and leaves the original allocation
121-
/// intact.
122-
///
123-
/// If the allocation was relocated, the memory at the passed-in pointer is
124-
/// undefined after the call.
125-
///
126-
/// Behavior is undefined if the requested size is 0 or the alignment is not a
127-
/// power of 2. The alignment must be no larger than the largest supported page
128-
/// size on the platform.
129-
///
130-
/// The `old_size` and `align` parameters are the parameters that were used to
131-
/// create the allocation referenced by `ptr`.
132-
///
133-
/// This function is optional. If not provided, an implementation will be
134-
/// generated which calls `allocate` to obtain a new buffer, copies the old
135-
/// memory contents to the new buffer, and then calls `deallocate` on the old
136-
/// buffer.
137-
#[allocator(reallocate)]
138-
pub fn reallocate(ptr: *mut u8, old_size: usize, size: usize, align: usize) -> *mut u8 {
147+
pub unsafe fn deallocate(&self, ptr: *mut u8, layout: Layout) {
139148
...
140149
}
141150

142-
/// Resizes the allocation referenced by `ptr` to `size` bytes without moving
143-
/// it.
144-
///
145-
/// The new size of the allocation is returned. This must be at least
146-
/// `old_size`. The allocation must always remain valid.
147-
///
148-
/// Behavior is undefined if the requested size is 0 or the alignment is not a
149-
/// power of 2. The alignment must be no larger than the largest supported page
150-
/// size on the platform.
151-
///
152-
/// The `old_size` and `align` parameters are the parameters that were used to
153-
/// create the allocation referenced by `ptr`.
154-
///
155-
/// This function is optional. The default implementation simply returns
156-
/// `old_size`.
157-
#[allocator(reallocate_inplace)]
158-
pub fn reallocate_inplace(ptr: *mut u8, old_size: usize, size: usize, align: usize) -> usize {
151+
pub fn reallocate(ptr: *mut u8, old_layout: Layout, layout: Layout) -> *mut u8 {
159152
...
160153
}
161154
```
162155

163-
Note that `useable_size` has been removed, as it is not used anywhere in the
164-
standard library.
156+
Each of these functions simply delegates to the selected global allocator. The
157+
allocator is selected by tagging a static value of a type implementing
158+
`GlobalAllocator` with the `#[allocator]` annotation:
165159

166-
The allocator functions must be publicly accessible, but can have any name and
167-
be defined in any module. However, it is recommended to use the names above in
168-
the crate root to minimize confusion.
169-
170-
Note that new functions can be added to this API in the future as long as they
171-
can have default implementations in a similar manner to other optional
172-
functions.
173-
174-
## Using an allocator
160+
```rust
161+
extern crate my_allocator;
175162

176-
The functions that an allocator crate defines can be called directly, but most
177-
usage will happen through the *global allocator* interface located in
178-
`std::heap`. This module exposes a set of functions identical to those described
179-
above, but that call into the global allocator. To select the global allocator,
180-
a crate declares it via an `extern crate` annotated with `#[allocator]`:
163+
use my_allocator::{MyAllocator, MY_ALLOCATOR_INIT};
181164

182-
```rust
183165
#[allocator]
184-
extern crate jemalloc;
166+
static ALLOCATOR: MyAllocator = MY_ALLOCATOR_INIT;
167+
168+
fn main() {
169+
...
170+
}
185171
```
186172

187-
As its name would suggest, the global allocator is a global resource - all
188-
crates in a dependency tree must agree on the selected global allocator. If two
189-
or more distinct allocator crates are selected, compilation will fail. Note that
190-
multiple crates can select a global allocator as long as that allocator is the
191-
same across all of them. In addition, a crate can depend on an allocator crate
192-
without declaring it to be the global allocator by omitting the `#[allocator]`
193-
annotation.
173+
Note that `ALLOCATOR` is still a normal static value - it can be used like any
174+
other static would bed.
194175

195176
## Standard library
196177

178+
A small `alloc_api` crate will be created which will contain the `Layout` type.
179+
The initial API will be more conservative than that described in [RFC 1398][],
180+
possibly nothing more than a `from_size_align` constructor and accessors for
181+
`size` and `align`.
182+
197183
The standard library will gain a new stable crate - `alloc_system`. This is the
198184
default allocator crate and corresponds to the "system" allocator (i.e. `malloc`
199185
etc on Unix and `HeapAlloc` etc on Windows).
200186

201187
The `alloc::heap` module will be reexported in `std` and stabilized. It will
202188
simply contain functions matching directly to those defined by the allocator
203189
API. The `alloc` crate itself may also be stabilized at a later date, but this
204-
RFC does not propose that.
190+
RFC does not propose that. `Layout` will be reexported in the `heap` module.
205191

206192
The existing `alloc_jemalloc` may continue to exist as an implementation detail
207193
of the Rust compiler, but it will never be stabilized. Applications wishing to
@@ -245,23 +231,14 @@ all of the requirements are met.
245231
# Alternatives
246232
[alternatives]: #alternatives
247233

248-
We could require that at most one crate selects a global allocator in the crate
249-
graph, which may simplify the implementation.
250-
251-
The allocator APIs could be simplified to a more "traditional"
252-
malloc/calloc/free API at the cost of an efficiency loss when using allocators
253-
with more powerful APIs.
234+
We could loosen the requirement that the root crate is the only one which may
235+
select the global allocator in favor of allowing any crate in the dependency
236+
graph to do so.
254237

255-
The global allocator could be an instance of the `Allocator` trait. Since that
256-
trait's methods take `&mut self`, things are a bit complicated however. The
257-
allocator would most likely need to be a `const` type implementing `Allocator`
258-
since it wouldn't be sound to interact with a static. This may cause confusion
259-
since the allocator itself will therefor not be allowed to maintain any state
260-
internally since a new instance will be created for each use. In addition, the
261-
`Allocator` trait uses a `Layout` type as a higher level encapsulation of the
262-
requested alignment and size of the allocation. The larger API surface area
263-
will most likely cause this feature to have a significantly longer stabilization
264-
period.
238+
We could try to use the `Allocator` trait for global allocators. The `&mut self`
239+
problem can b e solved via an implementation on a reference to the allocator
240+
type in a way similar to `TcpStream`'s `Write` and `Read` implementations, but
241+
this is pretty hacky.
265242

266243
# Unresolved questions
267244
[unresolved]: #unresolved-questions
@@ -277,6 +254,7 @@ implementations here:
277254
specific messaging that may exist.
278255
* `usable_size`, which is mentioned above as being unused, and should probably
279256
be removed from this trait as well.
257+
* `realloc_inplace`, which attempts to resize an allocation without moving it.
280258
* `alloc_excess`, which is like `alloc` but returns the entire usable size
281259
including any extra space beyond the requested size.
282260
* Some other higher level convenience methods like `alloc_array`.

0 commit comments

Comments
 (0)