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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
use std::{
fmt::Debug,
mem::{align_of, size_of, size_of_val},
};
use memmap2::MmapMut;
use super::{LinearMemory, PAGE_SIZE};
pub const CELL_STRIDE: usize = 1;
/// Mmap-backed linear memory. OS-memory pages are paged in on-demand and zero-initialized.
#[derive(Debug)]
pub struct MmapMemory {
mmap: MmapMut,
size: usize,
}
impl Clone for MmapMemory {
fn clone(&self) -> Self {
let mut new_mmap = MmapMut::map_anon(self.mmap.len()).unwrap();
new_mmap.copy_from_slice(&self.mmap);
Self {
mmap: new_mmap,
size: self.size,
}
}
}
impl MmapMemory {
#[inline(always)]
pub fn as_ptr(&self) -> *const u8 {
self.mmap.as_ptr()
}
#[inline(always)]
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.mmap.as_mut_ptr()
}
#[cfg(not(feature = "unprotected"))]
#[inline(always)]
fn check_bounds(&self, start: usize, size: usize) {
let memory_size = self.size();
if start > memory_size || size > memory_size - start {
panic_oob(start, size, memory_size);
}
}
#[cfg(feature = "unprotected")]
#[inline(always)]
fn check_bounds(&self, start: usize, size: usize) {
let memory_size = self.size();
debug_assert!(
start <= memory_size && size <= memory_size - start,
"Memory access out of bounds: start={} size={} memory_size={}",
start,
size,
memory_size
);
}
}
impl LinearMemory for MmapMemory {
/// Create a new MmapMemory with the given `size` in bytes.
/// We round `size` up to be a multiple of the mmap page size (4kb by default).
fn new(size: usize) -> Self {
let mmap_size = size.div_ceil(PAGE_SIZE) * PAGE_SIZE;
// anonymous mapping means pages are zero-initialized on first use
Self {
mmap: MmapMut::map_anon(mmap_size).unwrap(),
size,
}
}
fn size(&self) -> usize {
self.size
}
fn as_slice(&self) -> &[u8] {
&self.mmap[..self.size]
}
fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.mmap[..self.size]
}
#[cfg(target_os = "linux")]
fn fill_zero(&mut self) {
use libc::{madvise, MADV_DONTNEED};
let mmap = &mut self.mmap;
// SAFETY: our mmap is a memory-backed (not file-backed) anonymous private mapping.
// When we madvise MADV_DONTNEED, according to https://man7.org/linux/man-pages/man2/madvise.2.html
// > subsequent accesses of pages in the range will succeed, but
// > will result in either repopulating the memory contents from
// > the up-to-date contents of the underlying mapped file (for
// > shared file mappings, shared anonymous mappings, and shmem-
// > based techniques such as System V shared memory segments)
// > or zero-fill-on-demand pages for anonymous private
// > mappings.
unsafe {
let ret = madvise(
mmap.as_ptr() as *mut libc::c_void,
mmap.len(),
MADV_DONTNEED,
);
if ret != 0 {
// Fallback to write_bytes if madvise fails
std::ptr::write_bytes(mmap.as_mut_ptr(), 0, mmap.len());
}
}
}
#[inline(always)]
unsafe fn read<BLOCK: Copy>(&self, from: usize) -> BLOCK {
self.check_bounds(from, size_of::<BLOCK>());
let src = self.as_ptr().add(from) as *const BLOCK;
// SAFETY:
// - Bounds checked above (unless unprotected feature enabled)
// - We assume `src` is aligned to `BLOCK`
// - We assume `BLOCK` is "plain old data" so the underlying `src` bytes is valid to read as
// an initialized value of `BLOCK`
core::ptr::read(src)
}
#[inline(always)]
unsafe fn read_unaligned<BLOCK: Copy>(&self, from: usize) -> BLOCK {
self.check_bounds(from, size_of::<BLOCK>());
let src = self.as_ptr().add(from) as *const BLOCK;
// SAFETY:
// - Bounds checked above (unless unprotected feature enabled)
// - We assume `BLOCK` is "plain old data" so the underlying `src` bytes is valid to read as
// an initialized value of `BLOCK`
core::ptr::read_unaligned(src)
}
#[inline(always)]
unsafe fn write<BLOCK: Copy>(&mut self, start: usize, values: BLOCK) {
self.check_bounds(start, size_of::<BLOCK>());
let dst = self.as_mut_ptr().add(start) as *mut BLOCK;
// SAFETY:
// - Bounds checked above (unless unprotected feature enabled)
// - We assume `dst` is aligned to `BLOCK`
core::ptr::write(dst, values);
}
#[inline(always)]
unsafe fn write_unaligned<BLOCK: Copy>(&mut self, start: usize, values: BLOCK) {
self.check_bounds(start, size_of::<BLOCK>());
let dst = self.as_mut_ptr().add(start) as *mut BLOCK;
// SAFETY:
// - Bounds checked above (unless unprotected feature enabled)
core::ptr::write_unaligned(dst, values);
}
#[inline(always)]
unsafe fn swap<BLOCK: Copy>(&mut self, start: usize, values: &mut BLOCK) {
self.check_bounds(start, size_of::<BLOCK>());
// SAFETY:
// - Bounds checked above (unless unprotected feature enabled)
// - We assume `start` is aligned to `BLOCK`
core::ptr::swap(
self.as_mut_ptr().add(start) as *mut BLOCK,
values as *mut BLOCK,
);
}
#[inline(always)]
unsafe fn copy_nonoverlapping<T: Copy>(&mut self, to: usize, data: &[T]) {
self.check_bounds(to, size_of_val(data));
debug_assert_eq!(PAGE_SIZE % align_of::<T>(), 0);
let src = data.as_ptr();
let dst = self.as_mut_ptr().add(to) as *mut T;
// SAFETY:
// - Bounds checked above (unless unprotected feature enabled)
// - Assumes `to` is aligned to `T` and `self.as_mut_ptr()` is aligned to `T`, which implies
// the same for `dst`.
core::ptr::copy_nonoverlapping::<T>(src, dst, data.len());
}
#[inline(always)]
unsafe fn get_aligned_slice<T: Copy>(&self, start: usize, len: usize) -> &[T] {
self.check_bounds(start, len * size_of::<T>());
let data = self.as_ptr().add(start) as *const T;
// SAFETY:
// - Bounds checked above (unless unprotected feature enabled)
// - Assumes `data` is aligned to `T`
// - `T` is "plain old data" (POD), so conversion from underlying bytes is properly
// initialized
// - `self` will not be mutated while borrowed
core::slice::from_raw_parts(data, len)
}
}
#[cold]
#[inline(never)]
fn panic_oob(start: usize, size: usize, memory_size: usize) -> ! {
panic!("Memory access out of bounds: start={start} size={size} memory_size={memory_size}");
}