ehooks.h revision 1.1 1 1.1 christos #ifndef JEMALLOC_INTERNAL_EHOOKS_H
2 1.1 christos #define JEMALLOC_INTERNAL_EHOOKS_H
3 1.1 christos
4 1.1 christos #include "jemalloc/internal/atomic.h"
5 1.1 christos #include "jemalloc/internal/extent_mmap.h"
6 1.1 christos
7 1.1 christos /*
8 1.1 christos * This module is the internal interface to the extent hooks (both
9 1.1 christos * user-specified and external). Eventually, this will give us the flexibility
10 1.1 christos * to use multiple different versions of user-visible extent-hook APIs under a
11 1.1 christos * single user interface.
12 1.1 christos *
13 1.1 christos * Current API expansions (not available to anyone but the default hooks yet):
14 1.1 christos * - Head state tracking. Hooks can decide whether or not to merge two
15 1.1 christos * extents based on whether or not one of them is the head (i.e. was
16 1.1 christos * allocated on its own). The later extent loses its "head" status.
17 1.1 christos */
18 1.1 christos
19 1.1 christos extern const extent_hooks_t ehooks_default_extent_hooks;
20 1.1 christos
21 1.1 christos typedef struct ehooks_s ehooks_t;
22 1.1 christos struct ehooks_s {
23 1.1 christos /*
24 1.1 christos * The user-visible id that goes with the ehooks (i.e. that of the base
25 1.1 christos * they're a part of, the associated arena's index within the arenas
26 1.1 christos * array).
27 1.1 christos */
28 1.1 christos unsigned ind;
29 1.1 christos /* Logically an extent_hooks_t *. */
30 1.1 christos atomic_p_t ptr;
31 1.1 christos };
32 1.1 christos
33 1.1 christos extern const extent_hooks_t ehooks_default_extent_hooks;
34 1.1 christos
35 1.1 christos /*
36 1.1 christos * These are not really part of the public API. Each hook has a fast-path for
37 1.1 christos * the default-hooks case that can avoid various small inefficiencies:
38 1.1 christos * - Forgetting tsd and then calling tsd_get within the hook.
39 1.1 christos * - Getting more state than necessary out of the extent_t.
40 1.1 christos * - Doing arena_ind -> arena -> arena_ind lookups.
41 1.1 christos * By making the calls to these functions visible to the compiler, it can move
42 1.1 christos * those extra bits of computation down below the fast-paths where they get ignored.
43 1.1 christos */
44 1.1 christos void *ehooks_default_alloc_impl(tsdn_t *tsdn, void *new_addr, size_t size,
45 1.1 christos size_t alignment, bool *zero, bool *commit, unsigned arena_ind);
46 1.1 christos bool ehooks_default_dalloc_impl(void *addr, size_t size);
47 1.1 christos void ehooks_default_destroy_impl(void *addr, size_t size);
48 1.1 christos bool ehooks_default_commit_impl(void *addr, size_t offset, size_t length);
49 1.1 christos bool ehooks_default_decommit_impl(void *addr, size_t offset, size_t length);
50 1.1 christos #ifdef PAGES_CAN_PURGE_LAZY
51 1.1 christos bool ehooks_default_purge_lazy_impl(void *addr, size_t offset, size_t length);
52 1.1 christos #endif
53 1.1 christos #ifdef PAGES_CAN_PURGE_FORCED
54 1.1 christos bool ehooks_default_purge_forced_impl(void *addr, size_t offset, size_t length);
55 1.1 christos #endif
56 1.1 christos bool ehooks_default_split_impl(void);
57 1.1 christos /*
58 1.1 christos * Merge is the only default extent hook we declare -- see the comment in
59 1.1 christos * ehooks_merge.
60 1.1 christos */
61 1.1 christos bool ehooks_default_merge(extent_hooks_t *extent_hooks, void *addr_a,
62 1.1 christos size_t size_a, void *addr_b, size_t size_b, bool committed,
63 1.1 christos unsigned arena_ind);
64 1.1 christos bool ehooks_default_merge_impl(tsdn_t *tsdn, void *addr_a, void *addr_b);
65 1.1 christos void ehooks_default_zero_impl(void *addr, size_t size);
66 1.1 christos void ehooks_default_guard_impl(void *guard1, void *guard2);
67 1.1 christos void ehooks_default_unguard_impl(void *guard1, void *guard2);
68 1.1 christos
69 1.1 christos /*
70 1.1 christos * We don't officially support reentrancy from wtihin the extent hooks. But
71 1.1 christos * various people who sit within throwing distance of the jemalloc team want
72 1.1 christos * that functionality in certain limited cases. The default reentrancy guards
73 1.1 christos * assert that we're not reentrant from a0 (since it's the bootstrap arena,
74 1.1 christos * where reentrant allocations would be redirected), which we would incorrectly
75 1.1 christos * trigger in cases where a0 has extent hooks (those hooks themselves can't be
76 1.1 christos * reentrant, then, but there are reasonable uses for such functionality, like
77 1.1 christos * putting internal metadata on hugepages). Therefore, we use the raw
78 1.1 christos * reentrancy guards.
79 1.1 christos *
80 1.1 christos * Eventually, we need to think more carefully about whether and where we
81 1.1 christos * support allocating from within extent hooks (and what that means for things
82 1.1 christos * like profiling, stats collection, etc.), and document what the guarantee is.
83 1.1 christos */
84 1.1 christos static inline void
85 1.1 christos ehooks_pre_reentrancy(tsdn_t *tsdn) {
86 1.1 christos tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn);
87 1.1 christos tsd_pre_reentrancy_raw(tsd);
88 1.1 christos }
89 1.1 christos
90 1.1 christos static inline void
91 1.1 christos ehooks_post_reentrancy(tsdn_t *tsdn) {
92 1.1 christos tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn);
93 1.1 christos tsd_post_reentrancy_raw(tsd);
94 1.1 christos }
95 1.1 christos
96 1.1 christos /* Beginning of the public API. */
97 1.1 christos void ehooks_init(ehooks_t *ehooks, extent_hooks_t *extent_hooks, unsigned ind);
98 1.1 christos
99 1.1 christos static inline unsigned
100 1.1 christos ehooks_ind_get(const ehooks_t *ehooks) {
101 1.1 christos return ehooks->ind;
102 1.1 christos }
103 1.1 christos
104 1.1 christos static inline void
105 1.1 christos ehooks_set_extent_hooks_ptr(ehooks_t *ehooks, extent_hooks_t *extent_hooks) {
106 1.1 christos atomic_store_p(&ehooks->ptr, extent_hooks, ATOMIC_RELEASE);
107 1.1 christos }
108 1.1 christos
109 1.1 christos static inline extent_hooks_t *
110 1.1 christos ehooks_get_extent_hooks_ptr(ehooks_t *ehooks) {
111 1.1 christos return (extent_hooks_t *)atomic_load_p(&ehooks->ptr, ATOMIC_ACQUIRE);
112 1.1 christos }
113 1.1 christos
114 1.1 christos static inline bool
115 1.1 christos ehooks_are_default(ehooks_t *ehooks) {
116 1.1 christos return ehooks_get_extent_hooks_ptr(ehooks) ==
117 1.1 christos &ehooks_default_extent_hooks;
118 1.1 christos }
119 1.1 christos
120 1.1 christos /*
121 1.1 christos * In some cases, a caller needs to allocate resources before attempting to call
122 1.1 christos * a hook. If that hook is doomed to fail, this is wasteful. We therefore
123 1.1 christos * include some checks for such cases.
124 1.1 christos */
125 1.1 christos static inline bool
126 1.1 christos ehooks_dalloc_will_fail(ehooks_t *ehooks) {
127 1.1 christos if (ehooks_are_default(ehooks)) {
128 1.1 christos return opt_retain;
129 1.1 christos } else {
130 1.1 christos return ehooks_get_extent_hooks_ptr(ehooks)->dalloc == NULL;
131 1.1 christos }
132 1.1 christos }
133 1.1 christos
134 1.1 christos static inline bool
135 1.1 christos ehooks_split_will_fail(ehooks_t *ehooks) {
136 1.1 christos return ehooks_get_extent_hooks_ptr(ehooks)->split == NULL;
137 1.1 christos }
138 1.1 christos
139 1.1 christos static inline bool
140 1.1 christos ehooks_merge_will_fail(ehooks_t *ehooks) {
141 1.1 christos return ehooks_get_extent_hooks_ptr(ehooks)->merge == NULL;
142 1.1 christos }
143 1.1 christos
144 1.1 christos static inline bool
145 1.1 christos ehooks_guard_will_fail(ehooks_t *ehooks) {
146 1.1 christos /*
147 1.1 christos * Before the guard hooks are officially introduced, limit the use to
148 1.1 christos * the default hooks only.
149 1.1 christos */
150 1.1 christos return !ehooks_are_default(ehooks);
151 1.1 christos }
152 1.1 christos
153 1.1 christos /*
154 1.1 christos * Some hooks are required to return zeroed memory in certain situations. In
155 1.1 christos * debug mode, we do some heuristic checks that they did what they were supposed
156 1.1 christos * to.
157 1.1 christos *
158 1.1 christos * This isn't really ehooks-specific (i.e. anyone can check for zeroed memory).
159 1.1 christos * But incorrect zero information indicates an ehook bug.
160 1.1 christos */
161 1.1 christos static inline void
162 1.1 christos ehooks_debug_zero_check(void *addr, size_t size) {
163 1.1 christos assert(((uintptr_t)addr & PAGE_MASK) == 0);
164 1.1 christos assert((size & PAGE_MASK) == 0);
165 1.1 christos assert(size > 0);
166 1.1 christos if (config_debug) {
167 1.1 christos /* Check the whole first page. */
168 1.1 christos size_t *p = (size_t *)addr;
169 1.1 christos for (size_t i = 0; i < PAGE / sizeof(size_t); i++) {
170 1.1 christos assert(p[i] == 0);
171 1.1 christos }
172 1.1 christos /*
173 1.1 christos * And 4 spots within. There's a tradeoff here; the larger
174 1.1 christos * this number, the more likely it is that we'll catch a bug
175 1.1 christos * where ehooks return a sparsely non-zero range. But
176 1.1 christos * increasing the number of checks also increases the number of
177 1.1 christos * page faults in debug mode. FreeBSD does much of their
178 1.1 christos * day-to-day development work in debug mode, so we don't want
179 1.1 christos * even the debug builds to be too slow.
180 1.1 christos */
181 1.1 christos const size_t nchecks = 4;
182 1.1 christos assert(PAGE >= sizeof(size_t) * nchecks);
183 1.1 christos for (size_t i = 0; i < nchecks; ++i) {
184 1.1 christos assert(p[i * (size / sizeof(size_t) / nchecks)] == 0);
185 1.1 christos }
186 1.1 christos }
187 1.1 christos }
188 1.1 christos
189 1.1 christos
190 1.1 christos static inline void *
191 1.1 christos ehooks_alloc(tsdn_t *tsdn, ehooks_t *ehooks, void *new_addr, size_t size,
192 1.1 christos size_t alignment, bool *zero, bool *commit) {
193 1.1 christos bool orig_zero = *zero;
194 1.1 christos void *ret;
195 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
196 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
197 1.1 christos ret = ehooks_default_alloc_impl(tsdn, new_addr, size,
198 1.1 christos alignment, zero, commit, ehooks_ind_get(ehooks));
199 1.1 christos } else {
200 1.1 christos ehooks_pre_reentrancy(tsdn);
201 1.1 christos ret = extent_hooks->alloc(extent_hooks, new_addr, size,
202 1.1 christos alignment, zero, commit, ehooks_ind_get(ehooks));
203 1.1 christos ehooks_post_reentrancy(tsdn);
204 1.1 christos }
205 1.1 christos assert(new_addr == NULL || ret == NULL || new_addr == ret);
206 1.1 christos assert(!orig_zero || *zero);
207 1.1 christos if (*zero && ret != NULL) {
208 1.1 christos ehooks_debug_zero_check(ret, size);
209 1.1 christos }
210 1.1 christos return ret;
211 1.1 christos }
212 1.1 christos
213 1.1 christos static inline bool
214 1.1 christos ehooks_dalloc(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size,
215 1.1 christos bool committed) {
216 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
217 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
218 1.1 christos return ehooks_default_dalloc_impl(addr, size);
219 1.1 christos } else if (extent_hooks->dalloc == NULL) {
220 1.1 christos return true;
221 1.1 christos } else {
222 1.1 christos ehooks_pre_reentrancy(tsdn);
223 1.1 christos bool err = extent_hooks->dalloc(extent_hooks, addr, size,
224 1.1 christos committed, ehooks_ind_get(ehooks));
225 1.1 christos ehooks_post_reentrancy(tsdn);
226 1.1 christos return err;
227 1.1 christos }
228 1.1 christos }
229 1.1 christos
230 1.1 christos static inline void
231 1.1 christos ehooks_destroy(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size,
232 1.1 christos bool committed) {
233 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
234 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
235 1.1 christos ehooks_default_destroy_impl(addr, size);
236 1.1 christos } else if (extent_hooks->destroy == NULL) {
237 1.1 christos /* Do nothing. */
238 1.1 christos } else {
239 1.1 christos ehooks_pre_reentrancy(tsdn);
240 1.1 christos extent_hooks->destroy(extent_hooks, addr, size, committed,
241 1.1 christos ehooks_ind_get(ehooks));
242 1.1 christos ehooks_post_reentrancy(tsdn);
243 1.1 christos }
244 1.1 christos }
245 1.1 christos
246 1.1 christos static inline bool
247 1.1 christos ehooks_commit(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size,
248 1.1 christos size_t offset, size_t length) {
249 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
250 1.1 christos bool err;
251 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
252 1.1 christos err = ehooks_default_commit_impl(addr, offset, length);
253 1.1 christos } else if (extent_hooks->commit == NULL) {
254 1.1 christos err = true;
255 1.1 christos } else {
256 1.1 christos ehooks_pre_reentrancy(tsdn);
257 1.1 christos err = extent_hooks->commit(extent_hooks, addr, size,
258 1.1 christos offset, length, ehooks_ind_get(ehooks));
259 1.1 christos ehooks_post_reentrancy(tsdn);
260 1.1 christos }
261 1.1 christos if (!err) {
262 1.1 christos ehooks_debug_zero_check(addr, size);
263 1.1 christos }
264 1.1 christos return err;
265 1.1 christos }
266 1.1 christos
267 1.1 christos static inline bool
268 1.1 christos ehooks_decommit(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size,
269 1.1 christos size_t offset, size_t length) {
270 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
271 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
272 1.1 christos return ehooks_default_decommit_impl(addr, offset, length);
273 1.1 christos } else if (extent_hooks->decommit == NULL) {
274 1.1 christos return true;
275 1.1 christos } else {
276 1.1 christos ehooks_pre_reentrancy(tsdn);
277 1.1 christos bool err = extent_hooks->decommit(extent_hooks, addr, size,
278 1.1 christos offset, length, ehooks_ind_get(ehooks));
279 1.1 christos ehooks_post_reentrancy(tsdn);
280 1.1 christos return err;
281 1.1 christos }
282 1.1 christos }
283 1.1 christos
284 1.1 christos static inline bool
285 1.1 christos ehooks_purge_lazy(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size,
286 1.1 christos size_t offset, size_t length) {
287 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
288 1.1 christos #ifdef PAGES_CAN_PURGE_LAZY
289 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
290 1.1 christos return ehooks_default_purge_lazy_impl(addr, offset, length);
291 1.1 christos }
292 1.1 christos #endif
293 1.1 christos if (extent_hooks->purge_lazy == NULL) {
294 1.1 christos return true;
295 1.1 christos } else {
296 1.1 christos ehooks_pre_reentrancy(tsdn);
297 1.1 christos bool err = extent_hooks->purge_lazy(extent_hooks, addr, size,
298 1.1 christos offset, length, ehooks_ind_get(ehooks));
299 1.1 christos ehooks_post_reentrancy(tsdn);
300 1.1 christos return err;
301 1.1 christos }
302 1.1 christos }
303 1.1 christos
304 1.1 christos static inline bool
305 1.1 christos ehooks_purge_forced(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size,
306 1.1 christos size_t offset, size_t length) {
307 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
308 1.1 christos /*
309 1.1 christos * It would be correct to have a ehooks_debug_zero_check call at the end
310 1.1 christos * of this function; purge_forced is required to zero. But checking
311 1.1 christos * would touch the page in question, which may have performance
312 1.1 christos * consequences (imagine the hooks are using hugepages, with a global
313 1.1 christos * zero page off). Even in debug mode, it's usually a good idea to
314 1.1 christos * avoid cases that can dramatically increase memory consumption.
315 1.1 christos */
316 1.1 christos #ifdef PAGES_CAN_PURGE_FORCED
317 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
318 1.1 christos return ehooks_default_purge_forced_impl(addr, offset, length);
319 1.1 christos }
320 1.1 christos #endif
321 1.1 christos if (extent_hooks->purge_forced == NULL) {
322 1.1 christos return true;
323 1.1 christos } else {
324 1.1 christos ehooks_pre_reentrancy(tsdn);
325 1.1 christos bool err = extent_hooks->purge_forced(extent_hooks, addr, size,
326 1.1 christos offset, length, ehooks_ind_get(ehooks));
327 1.1 christos ehooks_post_reentrancy(tsdn);
328 1.1 christos return err;
329 1.1 christos }
330 1.1 christos }
331 1.1 christos
332 1.1 christos static inline bool
333 1.1 christos ehooks_split(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size,
334 1.1 christos size_t size_a, size_t size_b, bool committed) {
335 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
336 1.1 christos if (ehooks_are_default(ehooks)) {
337 1.1 christos return ehooks_default_split_impl();
338 1.1 christos } else if (extent_hooks->split == NULL) {
339 1.1 christos return true;
340 1.1 christos } else {
341 1.1 christos ehooks_pre_reentrancy(tsdn);
342 1.1 christos bool err = extent_hooks->split(extent_hooks, addr, size, size_a,
343 1.1 christos size_b, committed, ehooks_ind_get(ehooks));
344 1.1 christos ehooks_post_reentrancy(tsdn);
345 1.1 christos return err;
346 1.1 christos }
347 1.1 christos }
348 1.1 christos
349 1.1 christos static inline bool
350 1.1 christos ehooks_merge(tsdn_t *tsdn, ehooks_t *ehooks, void *addr_a, size_t size_a,
351 1.1 christos void *addr_b, size_t size_b, bool committed) {
352 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
353 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
354 1.1 christos return ehooks_default_merge_impl(tsdn, addr_a, addr_b);
355 1.1 christos } else if (extent_hooks->merge == NULL) {
356 1.1 christos return true;
357 1.1 christos } else {
358 1.1 christos ehooks_pre_reentrancy(tsdn);
359 1.1 christos bool err = extent_hooks->merge(extent_hooks, addr_a, size_a,
360 1.1 christos addr_b, size_b, committed, ehooks_ind_get(ehooks));
361 1.1 christos ehooks_post_reentrancy(tsdn);
362 1.1 christos return err;
363 1.1 christos }
364 1.1 christos }
365 1.1 christos
366 1.1 christos static inline void
367 1.1 christos ehooks_zero(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size) {
368 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
369 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
370 1.1 christos ehooks_default_zero_impl(addr, size);
371 1.1 christos } else {
372 1.1 christos /*
373 1.1 christos * It would be correct to try using the user-provided purge
374 1.1 christos * hooks (since they are required to have zeroed the extent if
375 1.1 christos * they indicate success), but we don't necessarily know their
376 1.1 christos * cost. We'll be conservative and use memset.
377 1.1 christos */
378 1.1 christos memset(addr, 0, size);
379 1.1 christos }
380 1.1 christos }
381 1.1 christos
382 1.1 christos static inline bool
383 1.1 christos ehooks_guard(tsdn_t *tsdn, ehooks_t *ehooks, void *guard1, void *guard2) {
384 1.1 christos bool err;
385 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
386 1.1 christos
387 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
388 1.1 christos ehooks_default_guard_impl(guard1, guard2);
389 1.1 christos err = false;
390 1.1 christos } else {
391 1.1 christos err = true;
392 1.1 christos }
393 1.1 christos
394 1.1 christos return err;
395 1.1 christos }
396 1.1 christos
397 1.1 christos static inline bool
398 1.1 christos ehooks_unguard(tsdn_t *tsdn, ehooks_t *ehooks, void *guard1, void *guard2) {
399 1.1 christos bool err;
400 1.1 christos extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks);
401 1.1 christos
402 1.1 christos if (extent_hooks == &ehooks_default_extent_hooks) {
403 1.1 christos ehooks_default_unguard_impl(guard1, guard2);
404 1.1 christos err = false;
405 1.1 christos } else {
406 1.1 christos err = true;
407 1.1 christos }
408 1.1 christos
409 1.1 christos return err;
410 1.1 christos }
411 1.1 christos
412 1.1 christos #endif /* JEMALLOC_INTERNAL_EHOOKS_H */
413