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
//! A nullable allocator the same size as `std.mem.Allocator`.
use core::ffi::c_void;
use crate::{Alignment, AllocatorVTable, StdAllocator};
/// PORT NOTE: Zig stored `{ ptr: *anyopaque, vtable: ?*const VTable }` and
/// recovered the `Allocator` by null-checking the vtable. Rust models the same
/// thing directly — `vtable: Option<&'static AllocatorVTable>` carries the
/// niche, so the struct is identical in size to `StdAllocator`.
#[derive(Clone, Copy)]
pub struct NullableAllocator {
ptr: *mut c_void,
// Utilize the null pointer optimization on the vtable instead of
// the regular `ptr` because `ptr` may be undefined. Stored as the
// `&'static` it was constructed from so `get()` is a safe field copy.
vtable: Option<&'static AllocatorVTable>,
}
impl Default for NullableAllocator {
fn default() -> Self {
Self {
ptr: core::ptr::null_mut(),
vtable: None,
}
}
}
impl NullableAllocator {
/// A `NullableAllocator` with no backing allocator. `const` so it can be
/// used in `const` initializers (e.g. `ZigString.Slice::EMPTY`).
pub const NULL: NullableAllocator = NullableAllocator {
ptr: core::ptr::null_mut(),
vtable: None,
};
#[inline]
pub const fn null() -> NullableAllocator {
Self::NULL
}
/// Wraps the global mimalloc allocator (`bun.default_allocator`).
#[inline]
pub fn default_alloc() -> NullableAllocator {
Self::init(Some(crate::basic::C_ALLOCATOR))
}
/// True iff `allocator`'s vtable is the global mimalloc vtable.
#[inline]
pub fn is_default(alloc: StdAllocator) -> bool {
core::ptr::eq(alloc.vtable, crate::basic::C_ALLOCATOR.vtable)
}
#[inline]
pub fn init(alloc: Option<StdAllocator>) -> NullableAllocator {
match alloc {
Some(a) => Self {
ptr: a.ptr,
vtable: Some(a.vtable),
},
None => Self::default(),
}
}
#[inline]
pub fn is_null(&self) -> bool {
self.vtable.is_none()
}
#[inline]
pub fn is_wtf_allocator(&self) -> bool {
let Some(a) = self.get() else { return false };
crate::String::is_wtf_allocator(a)
}
#[inline]
pub fn get(&self) -> Option<StdAllocator> {
Some(StdAllocator {
ptr: self.ptr,
vtable: self.vtable?,
})
}
pub fn free(&self, bytes: &[u8]) {
if let Some(allocator) = self.get() {
if crate::String::is_wtf_allocator(allocator) {
// avoid calling `std.mem.Allocator.free` as it sets the memory to undefined
// SAFETY: `bytes` is reborrowed mutably only for the vtable signature; the
// WTF deallocator treats it as opaque (Zig passes `[]u8`).
let buf = unsafe {
core::slice::from_raw_parts_mut(bytes.as_ptr().cast_mut(), bytes.len())
};
allocator.raw_free(buf, Alignment::from_byte_units(1), 0);
return;
}
allocator.free(bytes);
}
}
}
const _: () = assert!(
core::mem::size_of::<NullableAllocator>() == core::mem::size_of::<StdAllocator>(),
"Expected the sizes to be the same."
);
// ported from: src/bun_alloc/NullableAllocator.zig