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