Skip to content

Commit 8cd55cd

Browse files
authored
Implement simple allocator using enif_{alloc,free} (#580)
Since we can only expect a 64bit alignment from the Erlang allocator, we have to ensure larger alignments by overallocating. The allocator for this case behaves the same way as the zigler "large beam allocator", see https://github.com/E-xyza/zigler/blob/main/priv/beam/allocator.zig. If the alignment is greater than 8, we allocate enough memory to store an additional pointer. The result of the initial allocation is then written immediately before the aligned pointer, which is returned from the allocator. When deallocating, we can retrieve the original pointer and pass it on to `enif_free`.
1 parent 03005f8 commit 8cd55cd

File tree

9 files changed

+71
-4
lines changed

9 files changed

+71
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ versions.
1111
## [unreleased]
1212

1313
### Added
14+
15+
- Add optional support for using Erlang's allocator as Rust's global allocator
16+
(#580).
17+
1418
### Fixed
1519
### Changed
1620

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ members = [
1212
"rustler_tests/native/rustler_compile_tests",
1313
"rustler_benchmarks/native/benchmark",
1414
]
15+
default-members = [
16+
"rustler",
17+
"rustler_codegen",
18+
"rustler_sys",
19+
]

rustler/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ big_integer = ["dep:num-bigint"]
1414
default = ["derive", "nif_version_2_15"]
1515
derive = ["rustler_codegen"]
1616
alternative_nif_init_name = []
17+
allocator = []
1718
nif_version_2_14 = ["rustler_sys/nif_version_2_14"]
1819
nif_version_2_15 = ["nif_version_2_14", "rustler_sys/nif_version_2_15"]
1920
nif_version_2_16 = ["nif_version_2_15", "rustler_sys/nif_version_2_16"]

rustler/src/alloc.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::alloc::{GlobalAlloc, Layout};
2+
3+
const SIZEOF_USIZE: usize = std::mem::size_of::<usize>();
4+
const MAX_ALIGN: usize = 8;
5+
6+
#[cfg(feature = "allocator")]
7+
#[global_allocator]
8+
static ALLOCATOR: EnifAllocator = EnifAllocator;
9+
10+
/// Allocator implementation that forwards all allocation calls to Erlang's allocator. Allows the
11+
/// memory usage to be tracked by the BEAM.
12+
pub struct EnifAllocator;
13+
14+
unsafe impl GlobalAlloc for EnifAllocator {
15+
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
16+
if layout.align() > MAX_ALIGN {
17+
// Overallocate and store the original pointer in memory immediately before the aligned
18+
// section.
19+
//
20+
// The requested size is chosen such that we can always get an aligned buffer of size
21+
// `layout.size()`: Ignoring `SIZEOF_USIZE`, there must always be an aligned pointer in
22+
// the interval `[ptr, layout.align())`, so in the worst case, we have to pad with
23+
// `layout.align() - 1`. The requirement for an additional `usize` just shifts the
24+
// problem without changing the padding requirement.
25+
let total_size = SIZEOF_USIZE + layout.size() + layout.align() - 1;
26+
let ptr = rustler_sys::enif_alloc(total_size) as *mut u8;
27+
28+
// Shift the returned pointer to make space for the original pointer
29+
let ptr1 = ptr.wrapping_add(SIZEOF_USIZE);
30+
31+
// Align the result to the requested alignment
32+
let aligned_ptr = ptr1.wrapping_add(ptr1.align_offset(layout.align()));
33+
34+
// Write the original pointer immediately in front of the aligned pointer
35+
let header = aligned_ptr.wrapping_sub(SIZEOF_USIZE);
36+
*(header as *mut usize) = ptr as usize;
37+
38+
aligned_ptr
39+
} else {
40+
rustler_sys::enif_alloc(layout.size()) as *mut u8
41+
}
42+
}
43+
44+
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
45+
let ptr = if layout.align() > MAX_ALIGN {
46+
// Retrieve the original pointer
47+
let header = ptr.wrapping_sub(SIZEOF_USIZE);
48+
let ptr = *(header as *mut usize);
49+
ptr as *mut rustler_sys::c_void
50+
} else {
51+
ptr as *mut rustler_sys::c_void
52+
};
53+
rustler_sys::enif_free(ptr);
54+
}
55+
}

rustler/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ pub mod wrapper;
2929
#[doc(hidden)]
3030
pub mod codegen_runtime;
3131

32+
mod alloc;
33+
3234
#[macro_use]
3335
pub mod types;
3436

rustler_tests/native/dynamic_load/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ path = "src/lib.rs"
99
crate-type = ["cdylib"]
1010

1111
[dependencies]
12-
rustler = { path = "../../../rustler", features = ["big_integer"] }
12+
rustler = { path = "../../../rustler", features = ["big_integer", "allocator"] }

rustler_tests/native/rustler_bigint_test/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ path = "src/lib.rs"
99
crate-type = ["cdylib"]
1010

1111
[dependencies]
12-
rustler = { path = "../../../rustler", features = ["big_integer"] }
12+
rustler = { path = "../../../rustler", features = ["big_integer", "allocator"] }

rustler_tests/native/rustler_compile_tests/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ path = "src/lib.rs"
1010
crate-type = ["cdylib"]
1111

1212
[dependencies]
13-
rustler = { path = "../../../rustler" }
13+
rustler = { path = "../../../rustler", features = ["allocator"] }

rustler_tests/native/rustler_test/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ nif_version_2_16 = ["nif_version_2_15", "rustler/nif_version_2_16"]
2020
nif_version_2_17 = ["nif_version_2_16", "rustler/nif_version_2_17"]
2121

2222
[dependencies]
23-
rustler = { path = "../../../rustler" }
23+
rustler = { path = "../../../rustler", features = ["allocator"] }

0 commit comments

Comments
 (0)