-
Notifications
You must be signed in to change notification settings - Fork 13.3k
book: Add documentation on custom allocators #28869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
% Custom Allocators | ||
|
||
Allocating memory isn't always the easiest thing to do, and while Rust generally | ||
takes care of this by default it often becomes necessary to customize how | ||
allocation occurs. The compiler and standard library currently allow switching | ||
out the default global allocator in use at compile time. The design is currently | ||
spelled out in [RFC 1183][rfc] but this will walk you through how to get your | ||
own allocator up and running. | ||
|
||
[rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1183-swap-out-jemalloc.md | ||
|
||
# Default Allocator | ||
|
||
The compiler currently ships two default allocators: `alloc_system` and | ||
`alloc_jemalloc` (some targets don't have jemalloc, however). These allocators | ||
are just normal Rust crates and contain an implementation of the routines to | ||
allocate and deallocate memory. The standard library is not compiled assuming | ||
either one, and the compiler will decide which allocator is in use at | ||
compile-time depending on the type of output artifact being produced. | ||
|
||
Binaries generated by the compiler will use `alloc_jemalloc` by default (where | ||
available). In this situation the compiler "controls the world" in the sense of | ||
it has power over the final link. Primarily this means that the allocator | ||
decision can be left up the compiler. | ||
|
||
Dynamic and static libraries, however, will use `alloc_system` by default. Here | ||
Rust is typically a 'guest' in another application or another world where it | ||
cannot authoritatively decide what allocator is in use. As a result it resorts | ||
back to the standard APIs (e.g. `malloc` and `free`) for acquiring and releasing | ||
memory. | ||
|
||
# Switching Allocators | ||
|
||
Although the compiler's default choices may work most of the time, it's often | ||
necessary to tweak certain aspects. Overriding the compiler's decision about | ||
which allocator is in use is done simply by linking to the desired allocator: | ||
|
||
```rust,no_run | ||
#![feature(alloc_system)] | ||
|
||
extern crate alloc_system; | ||
|
||
fn main() { | ||
let a = Box::new(4); // allocates from the system allocator | ||
println!("{}", a); | ||
} | ||
``` | ||
|
||
In this example the binary generated will not link to jemalloc by default but | ||
instead use the system allocator. Conversely to generate a dynamic library which | ||
uses jemalloc by default one would write: | ||
|
||
```rust,ignore | ||
#![feature(alloc_jemalloc)] | ||
#![crate_type = "dylib"] | ||
|
||
extern crate alloc_jemalloc; | ||
|
||
pub fn foo() { | ||
let a = Box::new(4); // allocates from jemalloc | ||
println!("{}", a); | ||
} | ||
# fn main() {} | ||
``` | ||
|
||
# Writing a custom allocator | ||
|
||
Sometimes even the choices of jemalloc vs the system allocator aren't enough and | ||
an entirely new custom allocator is required. In this you'll write your own | ||
crate which implements the allocator API (e.g. the same as `alloc_system` or | ||
`alloc_jemalloc`). As an example, let's take a look at a simplified and | ||
annotated version of `alloc_system` | ||
|
||
```rust,no_run | ||
# // only needed for rustdoc --test down below | ||
# #![feature(lang_items)] | ||
// The compiler needs to be instructed that this crate is an allocator in order | ||
// to realize that when this is linked in another allocator like jemalloc should | ||
// not be linked in | ||
#![feature(allocator)] | ||
#![allocator] | ||
|
||
// Allocators are not allowed to depend on the standard library which in turn | ||
// requires an allocator in order to avoid circular dependencies. This crate, | ||
// however, can use all of libcore. | ||
#![feature(no_std)] | ||
#![no_std] | ||
|
||
// Let's give a unique name to our custom allocator | ||
#![crate_name = "my_allocator"] | ||
#![crate_type = "rlib"] | ||
|
||
// Our system allocator will use the in-tree libc crate for FFI bindings. Note | ||
// that currently the external (crates.io) libc cannot be used because it links | ||
// to the standard library (e.g. `#![no_std]` isn't stable yet), so that's why | ||
// this specifically requires the in-tree version. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😍 |
||
#![feature(libc)] | ||
extern crate libc; | ||
|
||
// Listed below are the five allocation functions currently required by custom | ||
// allocators. Their signatures and symbol names are not currently typechecked | ||
// by the compiler, but this is a future extension and are required to match | ||
// what is found below. | ||
// | ||
// Note that the standard `malloc` and `realloc` functions do not provide a way | ||
// to communicate alignment so this implementation would need to be improved | ||
// with respect to alignment in that aspect. | ||
|
||
#[no_mangle] | ||
pub extern fn __rust_allocate(size: usize, _align: usize) -> *mut u8 { | ||
unsafe { libc::malloc(size as libc::size_t) as *mut u8 } | ||
} | ||
|
||
#[no_mangle] | ||
pub extern fn __rust_deallocate(ptr: *mut u8, _old_size: usize, _align: usize) { | ||
unsafe { libc::free(ptr as *mut libc::c_void) } | ||
} | ||
|
||
#[no_mangle] | ||
pub extern fn __rust_reallocate(ptr: *mut u8, _old_size: usize, size: usize, | ||
_align: usize) -> *mut u8 { | ||
unsafe { | ||
libc::realloc(ptr as *mut libc::c_void, size as libc::size_t) as *mut u8 | ||
} | ||
} | ||
|
||
#[no_mangle] | ||
pub extern fn __rust_reallocate_inplace(_ptr: *mut u8, old_size: usize, | ||
_size: usize, _align: usize) -> usize { | ||
old_size // this api is not supported by libc | ||
} | ||
|
||
#[no_mangle] | ||
pub extern fn __rust_usable_size(size: usize, _align: usize) -> usize { | ||
size | ||
} | ||
|
||
# // just needed to get rustdoc to test this | ||
# fn main() {} | ||
# #[lang = "panic_fmt"] fn panic_fmt() {} | ||
# #[lang = "eh_personality"] fn eh_personality() {} | ||
# #[lang = "eh_unwind_resume"] extern fn eh_unwind_resume() {} | ||
``` | ||
|
||
After we compile this crate, it can be used as follows: | ||
|
||
```rust,ignore | ||
extern crate my_allocator; | ||
|
||
fn main() { | ||
let a = Box::new(8); // allocates memory via our custom allocator crate | ||
println!("{}", a); | ||
} | ||
``` | ||
|
||
# Custom allocator limitations | ||
|
||
There are a few restrictions when working with custom allocators which may cause | ||
compiler errors: | ||
|
||
* Any one artifact may only be linked to at most one allocator. Binaries, | ||
dylibs, and staticlibs must link to exactly one allocator, and if none have | ||
been explicitly chosen the compiler will choose one. On the other than rlibs | ||
do not need to link to an allocator (but still can). | ||
|
||
* A consumer of an allocator is tagged with `#![needs_allocator]` (e.g. the | ||
`liballoc` crate currently) and an `#[allocator]` crate cannot transitively | ||
depend on a crate which needs an allocator (e.g. circular dependencies are not | ||
allowed). This basically means that allocators must restrict themselves to | ||
libcore currently. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is declared to be a
dylib
, why ismain
present?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC Rustdoc has issues here, basically
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah this is just needed to appears rustdoc --test, the
dylib
above if for readers and thefn main
is needed because rustdoc overrides thecrate_type
in the source with a manual specification.