1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! An opt-in `#[global_allocator]` backed by the BEAM allocator.
//!
//! [`EnifAlloc`] routes every Rust heap allocation in the NIF library through
//! `enif_alloc`/`enif_free`. This matters for hot code upgrade: `enif_free` is
//! the one free path that is valid across two independently compiled builds of
//! the library (a stable C ABI), whereas Rust's default allocator is
//! build-private. Routing allocations through the VM allocator is therefore a
//! building block for carrying state across the upgrade boundary safely (see
//! `docs/UPGRADE.md`).
//!
//! ## Why direct-linked, not `dlsym`
//!
//! The rest of otter resolves `enif_*` symbols at run time with `dlsym` (via
//! the `enif_ffi` crate). A global allocator cannot: it may be called for the very
//! first Rust allocation, before any initialization code runs, so it must not
//! depend on a resolution step that itself allocates. Instead this module
//! **direct-links** the two functions it needs as `extern "C"`. The BEAM
//! exports them, and the dynamic linker binds them when the NIF `.so` is
//! `dlopen`'d (`RTLD_NOW`) — before `nif_init`, before any Rust code. There is
//! no catch-22.
//!
//! ## Inert until you opt in
//!
//! [`EnifAlloc`] is always available, but it is referenced — and therefore
//! pulls in the direct-linked `enif_alloc`/`enif_free` symbols — only when you
//! install it as the global allocator with [`enif_global_allocator!`]. Until
//! then dead-code elimination drops it, so otter still links into ordinary,
//! non-BEAM binaries (its own `cargo test`, doc builds, etc.).
//!
//! Once you *do* install it, those two symbols are undefined in the object file
//! and resolved only when the BEAM loads the `.so` — so a crate that invokes
//! the macro links **only** as a NIF cdylib hosted by the BEAM, never as an
//! ordinary executable.
//!
//! [`enif_global_allocator!`]: crate::enif_global_allocator
use ;
use c_void;
// Direct-linked, not resolved via `dlsym` — see module docs.
unsafe extern "C"
/// One machine word, used to stash the allocation's base pointer just below
/// the aligned pointer handed to the caller.
const HEADER: usize = ;
/// A [`GlobalAlloc`] backed by the BEAM allocator (`enif_alloc`/`enif_free`).
///
/// Install it in the final NIF cdylib with [`enif_global_allocator!`], or by
/// hand: `#[global_allocator] static A: EnifAlloc = EnifAlloc;`.
///
/// [`enif_global_allocator!`]: crate::enif_global_allocator
;
// `enif_alloc`, like `malloc`, takes no alignment argument and guarantees only
// max-fundamental alignment (8 bytes on Linux x86-64). To honor an arbitrary
// `Layout::align`, we over-allocate and stash the original base pointer in the
// machine word just below the aligned pointer we hand out, then recover it in
// `dealloc` — the pointer passed to `enif_free` must be exactly the one
// `enif_alloc` returned.
unsafe
/// Install [`EnifAlloc`] as the `#[global_allocator]` of the current crate.
///
/// Invoke this once in your NIF cdylib (where a `#[global_allocator]` is legal):
///
/// ```ignore
/// otter::enif_global_allocator!();
/// ```
///
/// Invoking this makes the crate link only as a BEAM-hosted cdylib (see the
/// [`alloc`](crate::alloc) module docs).