summaryrefslogtreecommitdiffstats
path: root/deps/jemalloc/include/jemalloc/internal/emap.h
blob: 847af3278de30917459323a7db71920a418e43cc (plain)
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
#ifndef JEMALLOC_INTERNAL_EMAP_H
#define JEMALLOC_INTERNAL_EMAP_H

#include "jemalloc/internal/base.h"
#include "jemalloc/internal/rtree.h"

/*
 * Note: Ends without at semicolon, so that
 *     EMAP_DECLARE_RTREE_CTX;
 * in uses will avoid empty-statement warnings.
 */
#define EMAP_DECLARE_RTREE_CTX						\
    rtree_ctx_t rtree_ctx_fallback;					\
    rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback)

typedef struct emap_s emap_t;
struct emap_s {
	rtree_t rtree;
};

/* Used to pass rtree lookup context down the path. */
typedef struct emap_alloc_ctx_t emap_alloc_ctx_t;
struct emap_alloc_ctx_t {
	szind_t szind;
	bool slab;
};

typedef struct emap_full_alloc_ctx_s emap_full_alloc_ctx_t;
struct emap_full_alloc_ctx_s {
	szind_t szind;
	bool slab;
	edata_t *edata;
};

bool emap_init(emap_t *emap, base_t *base, bool zeroed);

void emap_remap(tsdn_t *tsdn, emap_t *emap, edata_t *edata, szind_t szind,
    bool slab);

void emap_update_edata_state(tsdn_t *tsdn, emap_t *emap, edata_t *edata,
    extent_state_t state);

/*
 * The two acquire functions below allow accessing neighbor edatas, if it's safe
 * and valid to do so (i.e. from the same arena, of the same state, etc.).  This
 * is necessary because the ecache locks are state based, and only protect
 * edatas with the same state.  Therefore the neighbor edata's state needs to be
 * verified first, before chasing the edata pointer.  The returned edata will be
 * in an acquired state, meaning other threads will be prevented from accessing
 * it, even if technically the edata can still be discovered from the rtree.
 *
 * This means, at any moment when holding pointers to edata, either one of the
 * state based locks is held (and the edatas are all of the protected state), or
 * the edatas are in an acquired state (e.g. in active or merging state).  The
 * acquire operation itself (changing the edata to an acquired state) is done
 * under the state locks.
 */
edata_t *emap_try_acquire_edata_neighbor(tsdn_t *tsdn, emap_t *emap,
    edata_t *edata, extent_pai_t pai, extent_state_t expected_state,
    bool forward);
edata_t *emap_try_acquire_edata_neighbor_expand(tsdn_t *tsdn, emap_t *emap,
    edata_t *edata, extent_pai_t pai, extent_state_t expected_state);
void emap_release_edata(tsdn_t *tsdn, emap_t *emap, edata_t *edata,
    extent_state_t new_state);

/*
 * Associate the given edata with its beginning and end address, setting the
 * szind and slab info appropriately.
 * Returns true on error (i.e. resource exhaustion).
 */
bool emap_register_boundary(tsdn_t *tsdn, emap_t *emap, edata_t *edata,
    szind_t szind, bool slab);

/*
 * Does the same thing, but with the interior of the range, for slab
 * allocations.
 *
 * You might wonder why we don't just have a single emap_register function that
 * does both depending on the value of 'slab'.  The answer is twofold:
 * - As a practical matter, in places like the extract->split->commit pathway,
 *   we defer the interior operation until we're sure that the commit won't fail
 *   (but we have to register the split boundaries there).
 * - In general, we're trying to move to a world where the page-specific
 *   allocator doesn't know as much about how the pages it allocates will be
 *   used, and passing a 'slab' parameter everywhere makes that more
 *   complicated.
 *
 * Unlike the boundary version, this function can't fail; this is because slabs
 * can't get big enough to touch a new page that neither of the boundaries
 * touched, so no allocation is necessary to fill the interior once the boundary
 * has been touched.
 */
void emap_register_interior(tsdn_t *tsdn, emap_t *emap, edata_t *edata,
    szind_t szind);

void emap_deregister_boundary(tsdn_t *tsdn, emap_t *emap, edata_t *edata);
void emap_deregister_interior(tsdn_t *tsdn, emap_t *emap, edata_t *edata);

typedef struct emap_prepare_s emap_prepare_t;
struct emap_prepare_s {
	rtree_leaf_elm_t *lead_elm_a;
	rtree_leaf_elm_t *lead_elm_b;
	rtree_leaf_elm_t *trail_elm_a;
	rtree_leaf_elm_t *trail_elm_b;
};

/**
 * These functions the emap metadata management for merging, splitting, and
 * reusing extents.  In particular, they set the boundary mappings from
 * addresses to edatas.  If the result is going to be used as a slab, you
 * still need to call emap_register_interior on it, though.
 *
 * Remap simply changes the szind and slab status of an extent's boundary
 * mappings.  If the extent is not a slab, it doesn't bother with updating the
 * end mapping (since lookups only occur in the interior of an extent for
 * slabs).  Since the szind and slab status only make sense for active extents,
 * this should only be called while activating or deactivating an extent.
 *
 * Split and merge have a "prepare" and a "commit" portion.  The prepare portion
 * does the operations that can be done without exclusive access to the extent
 * in question, while the commit variant requires exclusive access to maintain
 * the emap invariants.  The only function that can fail is emap_split_prepare,
 * and it returns true on failure (at which point the caller shouldn't commit).
 *
 * In all cases, "lead" refers to the lower-addressed extent, and trail to the
 * higher-addressed one.  It's the caller's responsibility to set the edata
 * state appropriately.
 */
bool emap_split_prepare(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare,
    edata_t *edata, size_t size_a, edata_t *trail, size_t size_b);
void emap_split_commit(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare,
    edata_t *lead, size_t size_a, edata_t *trail, size_t size_b);
void emap_merge_prepare(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare,
    edata_t *lead, edata_t *trail);
void emap_merge_commit(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare,
    edata_t *lead, edata_t *trail);

/* Assert that the emap's view of the given edata matches the edata's view. */
void emap_do_assert_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata);
static inline void
emap_assert_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata) {
	if (config_debug) {
		emap_do_assert_mapped(tsdn, emap, edata);
	}
}

/* Assert that the given edata isn't in the map. */
void emap_do_assert_not_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata);
static inline void
emap_assert_not_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata) {
	if (config_debug) {
		emap_do_assert_not_mapped(tsdn, emap, edata);
	}
}

JEMALLOC_ALWAYS_INLINE bool
emap_edata_in_transition(tsdn_t *tsdn, emap_t *emap, edata_t *edata) {
	assert(config_debug);
	emap_assert_mapped(tsdn, emap, edata);

	EMAP_DECLARE_RTREE_CTX;
	rtree_contents_t contents = rtree_read(tsdn, &emap->rtree, rtree_ctx,
	    (uintptr_t)edata_base_get(edata));

	return edata_state_in_transition(contents.metadata.state);
}

JEMALLOC_ALWAYS_INLINE bool
emap_edata_is_acquired(tsdn_t *tsdn, emap_t *emap, edata_t *edata) {
	if (!config_debug) {
		/* For assertions only. */
		return false;
	}

	/*
	 * The edata is considered acquired if no other threads will attempt to
	 * read / write any fields from it.  This includes a few cases:
	 *
	 * 1) edata not hooked into emap yet -- This implies the edata just got
	 * allocated or initialized.
	 *
	 * 2) in an active or transition state -- In both cases, the edata can
	 * be discovered from the emap, however the state tracked in the rtree
	 * will prevent other threads from accessing the actual edata.
	 */
	EMAP_DECLARE_RTREE_CTX;
	rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, &emap->rtree,
	    rtree_ctx, (uintptr_t)edata_base_get(edata), /* dependent */ true,
	    /* init_missing */ false);
	if (elm == NULL) {
		return true;
	}
	rtree_contents_t contents = rtree_leaf_elm_read(tsdn, &emap->rtree, elm,
	    /* dependent */ true);
	if (contents.edata == NULL ||
	    contents.metadata.state == extent_state_active ||
	    edata_state_in_transition(contents.metadata.state)) {
		return true;
	}

	return false;
}

JEMALLOC_ALWAYS_INLINE void
extent_assert_can_coalesce(const edata_t *inner, const edata_t *outer) {
	assert(edata_arena_ind_get(inner) == edata_arena_ind_get(outer));
	assert(edata_pai_get(inner) == edata_pai_get(outer));
	assert(edata_committed_get(inner) == edata_committed_get(outer));
	assert(edata_state_get(inner) == extent_state_active);
	assert(edata_state_get(outer) == extent_state_merging);
	assert(!edata_guarded_get(inner) && !edata_guarded_get(outer));
	assert(edata_base_get(inner) == edata_past_get(outer) ||
	    edata_base_get(outer) == edata_past_get(inner));
}

JEMALLOC_ALWAYS_INLINE void
extent_assert_can_expand(const edata_t *original, const edata_t *expand) {
	assert(edata_arena_ind_get(original) == edata_arena_ind_get(expand));
	assert(edata_pai_get(original) == edata_pai_get(expand));
	assert(edata_state_get(original) == extent_state_active);
	assert(edata_state_get(expand) == extent_state_merging);
	assert(edata_past_get(original) == edata_base_get(expand));
}

JEMALLOC_ALWAYS_INLINE edata_t *
emap_edata_lookup(tsdn_t *tsdn, emap_t *emap, const void *ptr) {
	EMAP_DECLARE_RTREE_CTX;

	return rtree_read(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)ptr).edata;
}

/* Fills in alloc_ctx with the info in the map. */
JEMALLOC_ALWAYS_INLINE void
emap_alloc_ctx_lookup(tsdn_t *tsdn, emap_t *emap, const void *ptr,
    emap_alloc_ctx_t *alloc_ctx) {
	EMAP_DECLARE_RTREE_CTX;

	rtree_metadata_t metadata = rtree_metadata_read(tsdn, &emap->rtree,
	    rtree_ctx, (uintptr_t)ptr);
	alloc_ctx->szind = metadata.szind;
	alloc_ctx->slab = metadata.slab;
}

/* The pointer must be mapped. */
JEMALLOC_ALWAYS_INLINE void
emap_full_alloc_ctx_lookup(tsdn_t *tsdn, emap_t *emap, const void *ptr,
    emap_full_alloc_ctx_t *full_alloc_ctx) {
	EMAP_DECLARE_RTREE_CTX;

	rtree_contents_t contents = rtree_read(tsdn, &emap->rtree, rtree_ctx,
	    (uintptr_t)ptr);
	full_alloc_ctx->edata = contents.edata;
	full_alloc_ctx->szind = contents.metadata.szind;
	full_alloc_ctx->slab = contents.metadata.slab;
}

/*
 * The pointer is allowed to not be mapped.
 *
 * Returns true when the pointer is not present.
 */
JEMALLOC_ALWAYS_INLINE bool
emap_full_alloc_ctx_try_lookup(tsdn_t *tsdn, emap_t *emap, const void *ptr,
    emap_full_alloc_ctx_t *full_alloc_ctx) {
	EMAP_DECLARE_RTREE_CTX;

	rtree_contents_t contents;
	bool err = rtree_read_independent(tsdn, &emap->rtree, rtree_ctx,
	    (uintptr_t)ptr, &contents);
	if (err) {
		return true;
	}
	full_alloc_ctx->edata = contents.edata;
	full_alloc_ctx->szind = contents.metadata.szind;
	full_alloc_ctx->slab = contents.metadata.slab;
	return false;
}

/*
 * Only used on the fastpath of free.  Returns true when cannot be fulfilled by
 * fast path, e.g. when the metadata key is not cached.
 */
JEMALLOC_ALWAYS_INLINE bool
emap_alloc_ctx_try_lookup_fast(tsd_t *tsd, emap_t *emap, const void *ptr,
    emap_alloc_ctx_t *alloc_ctx) {
	/* Use the unsafe getter since this may gets called during exit. */
	rtree_ctx_t *rtree_ctx = tsd_rtree_ctxp_get_unsafe(tsd);

	rtree_metadata_t metadata;
	bool err = rtree_metadata_try_read_fast(tsd_tsdn(tsd), &emap->rtree,
	    rtree_ctx, (uintptr_t)ptr, &metadata);
	if (err) {
		return true;
	}
	alloc_ctx->szind = metadata.szind;
	alloc_ctx->slab = metadata.slab;
	return false;
}

/*
 * We want to do batch lookups out of the cache bins, which use
 * cache_bin_ptr_array_get to access the i'th element of the bin (since they
 * invert usual ordering in deciding what to flush).  This lets the emap avoid
 * caring about its caller's ordering.
 */
typedef const void *(*emap_ptr_getter)(void *ctx, size_t ind);
/*
 * This allows size-checking assertions, which we can only do while we're in the
 * process of edata lookups.
 */
typedef void (*emap_metadata_visitor)(void *ctx, emap_full_alloc_ctx_t *alloc_ctx);

typedef union emap_batch_lookup_result_u emap_batch_lookup_result_t;
union emap_batch_lookup_result_u {
	edata_t *edata;
	rtree_leaf_elm_t *rtree_leaf;
};

JEMALLOC_ALWAYS_INLINE void
emap_edata_lookup_batch(tsd_t *tsd, emap_t *emap, size_t nptrs,
    emap_ptr_getter ptr_getter, void *ptr_getter_ctx,
    emap_metadata_visitor metadata_visitor, void *metadata_visitor_ctx,
    emap_batch_lookup_result_t *result) {
	/* Avoids null-checking tsdn in the loop below. */
	util_assume(tsd != NULL);
	rtree_ctx_t *rtree_ctx = tsd_rtree_ctxp_get(tsd);

	for (size_t i = 0; i < nptrs; i++) {
		const void *ptr = ptr_getter(ptr_getter_ctx, i);
		/*
		 * Reuse the edatas array as a temp buffer, lying a little about
		 * the types.
		 */
		result[i].rtree_leaf = rtree_leaf_elm_lookup(tsd_tsdn(tsd),
		    &emap->rtree, rtree_ctx, (uintptr_t)ptr,
		    /* dependent */ true, /* init_missing */ false);
	}

	for (size_t i = 0; i < nptrs; i++) {
		rtree_leaf_elm_t *elm = result[i].rtree_leaf;
		rtree_contents_t contents = rtree_leaf_elm_read(tsd_tsdn(tsd),
		    &emap->rtree, elm, /* dependent */ true);
		result[i].edata = contents.edata;
		emap_full_alloc_ctx_t alloc_ctx;
		/*
		 * Not all these fields are read in practice by the metadata
		 * visitor.  But the compiler can easily optimize away the ones
		 * that aren't, so no sense in being incomplete.
		 */
		alloc_ctx.szind = contents.metadata.szind;
		alloc_ctx.slab = contents.metadata.slab;
		alloc_ctx.edata = contents.edata;
		metadata_visitor(metadata_visitor_ctx, &alloc_ctx);
	}
}

#endif /* JEMALLOC_INTERNAL_EMAP_H */