@@ -30,9 +30,10 @@ There are a couple of issues with the current approach:
30
30
31
31
A C-style ABI is error prone - nothing ensures that the signatures are correct,
32
32
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.
36
37
37
38
Since an allocator is automatically selected when it is pulled into the crate
38
39
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.
65
66
66
67
## Defining an allocator
67
68
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
71
74
72
75
``` 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
+ }
87
121
}
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.
88
127
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 {
102
140
...
103
141
}
104
142
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 {
115
144
...
116
145
}
117
146
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 ) {
139
148
...
140
149
}
141
150
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 {
159
152
...
160
153
}
161
154
```
162
155
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:
165
159
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;
175
162
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 };
181
164
182
- ``` rust
183
165
#[allocator]
184
- extern crate jemalloc;
166
+ static ALLOCATOR : MyAllocator = MY_ALLOCATOR_INIT ;
167
+
168
+ fn main () {
169
+ ...
170
+ }
185
171
```
186
172
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.
194
175
195
176
## Standard library
196
177
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
+
197
183
The standard library will gain a new stable crate - ` alloc_system ` . This is the
198
184
default allocator crate and corresponds to the "system" allocator (i.e. ` malloc `
199
185
etc on Unix and ` HeapAlloc ` etc on Windows).
200
186
201
187
The ` alloc::heap ` module will be reexported in ` std ` and stabilized. It will
202
188
simply contain functions matching directly to those defined by the allocator
203
189
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.
205
191
206
192
The existing ` alloc_jemalloc ` may continue to exist as an implementation detail
207
193
of the Rust compiler, but it will never be stabilized. Applications wishing to
@@ -245,23 +231,14 @@ all of the requirements are met.
245
231
# Alternatives
246
232
[ alternatives ] : #alternatives
247
233
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.
254
237
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.
265
242
266
243
# Unresolved questions
267
244
[ unresolved ] : #unresolved-questions
@@ -277,6 +254,7 @@ implementations here:
277
254
specific messaging that may exist.
278
255
* ` usable_size ` , which is mentioned above as being unused, and should probably
279
256
be removed from this trait as well.
257
+ * ` realloc_inplace ` , which attempts to resize an allocation without moving it.
280
258
* ` alloc_excess ` , which is like ` alloc ` but returns the entire usable size
281
259
including any extra space beyond the requested size.
282
260
* Some other higher level convenience methods like ` alloc_array ` .
0 commit comments