1 /*-------------------------------------------------------------------------
2  *
3  * generation.c
4  *	  Generational allocator definitions.
5  *
6  * Generation is a custom MemoryContext implementation designed for cases of
7  * chunks with similar lifespan.
8  *
9  * Portions Copyright (c) 2017-2018, PostgreSQL Global Development Group
10  *
11  * IDENTIFICATION
12  *	  src/backend/utils/mmgr/generation.c
13  *
14  *
15  *	This memory context is based on the assumption that the chunks are freed
16  *	roughly in the same order as they were allocated (FIFO), or in groups with
17  *	similar lifespan (generations - hence the name of the context). This is
18  *	typical for various queue-like use cases, i.e. when tuples are constructed,
19  *	processed and then thrown away.
20  *
21  *	The memory context uses a very simple approach to free space management.
22  *	Instead of a complex global freelist, each block tracks a number
23  *	of allocated and freed chunks. Freed chunks are not reused, and once all
24  *	chunks in a block are freed, the whole block is thrown away. When the
25  *	chunks allocated in the same block have similar lifespan, this works
26  *	very well and is very cheap.
27  *
28  *	The current implementation only uses a fixed block size - maybe it should
29  *	adapt a min/max block size range, and grow the blocks automatically.
30  *	It already uses dedicated blocks for oversized chunks.
31  *
32  *	XXX It might be possible to improve this by keeping a small freelist for
33  *	only a small number of recent blocks, but it's not clear it's worth the
34  *	additional complexity.
35  *
36  *-------------------------------------------------------------------------
37  */
38 
39 #include "postgres.h"
40 
41 #include "lib/ilist.h"
42 #include "utils/memdebug.h"
43 #include "utils/memutils.h"
44 
45 
46 #define Generation_BLOCKHDRSZ	MAXALIGN(sizeof(GenerationBlock))
47 #define Generation_CHUNKHDRSZ	sizeof(GenerationChunk)
48 
49 typedef struct GenerationBlock GenerationBlock; /* forward reference */
50 typedef struct GenerationChunk GenerationChunk;
51 
52 typedef void *GenerationPointer;
53 
54 /*
55  * GenerationContext is a simple memory context not reusing allocated chunks,
56  * and freeing blocks once all chunks are freed.
57  */
58 typedef struct GenerationContext
59 {
60 	MemoryContextData header;	/* Standard memory-context fields */
61 
62 	/* Generational context parameters */
63 	Size		blockSize;		/* standard block size */
64 
65 	GenerationBlock *block;		/* current (most recently allocated) block */
66 	dlist_head	blocks;			/* list of blocks */
67 } GenerationContext;
68 
69 /*
70  * GenerationBlock
71  *		GenerationBlock is the unit of memory that is obtained by generation.c
72  *		from malloc().  It contains one or more GenerationChunks, which are
73  *		the units requested by palloc() and freed by pfree().  GenerationChunks
74  *		cannot be returned to malloc() individually, instead pfree()
75  *		updates the free counter of the block and when all chunks in a block
76  *		are free the whole block is returned to malloc().
77  *
78  *		GenerationBlock is the header data for a block --- the usable space
79  *		within the block begins at the next alignment boundary.
80  */
81 struct GenerationBlock
82 {
83 	dlist_node	node;			/* doubly-linked list of blocks */
84 	Size		blksize;		/* allocated size of this block */
85 	int			nchunks;		/* number of chunks in the block */
86 	int			nfree;			/* number of free chunks */
87 	char	   *freeptr;		/* start of free space in this block */
88 	char	   *endptr;			/* end of space in this block */
89 };
90 
91 /*
92  * GenerationChunk
93  *		The prefix of each piece of memory in a GenerationBlock
94  *
95  * Note: to meet the memory context APIs, the payload area of the chunk must
96  * be maxaligned, and the "context" link must be immediately adjacent to the
97  * payload area (cf. GetMemoryChunkContext).  We simplify matters for this
98  * module by requiring sizeof(GenerationChunk) to be maxaligned, and then
99  * we can ensure things work by adding any required alignment padding before
100  * the pointer fields.  There is a static assertion below that the alignment
101  * is done correctly.
102  */
103 struct GenerationChunk
104 {
105 	/* size is always the size of the usable space in the chunk */
106 	Size		size;
107 #ifdef MEMORY_CONTEXT_CHECKING
108 	/* when debugging memory usage, also store actual requested size */
109 	/* this is zero in a free chunk */
110 	Size		requested_size;
111 
112 #define GENERATIONCHUNK_RAWSIZE  (SIZEOF_SIZE_T * 2 + SIZEOF_VOID_P * 2)
113 #else
114 #define GENERATIONCHUNK_RAWSIZE  (SIZEOF_SIZE_T + SIZEOF_VOID_P * 2)
115 #endif							/* MEMORY_CONTEXT_CHECKING */
116 
117 	/* ensure proper alignment by adding padding if needed */
118 #if (GENERATIONCHUNK_RAWSIZE % MAXIMUM_ALIGNOF) != 0
119 	char		padding[MAXIMUM_ALIGNOF - GENERATIONCHUNK_RAWSIZE % MAXIMUM_ALIGNOF];
120 #endif
121 
122 	GenerationBlock *block;		/* block owning this chunk */
123 	GenerationContext *context; /* owning context, or NULL if freed chunk */
124 	/* there must not be any padding to reach a MAXALIGN boundary here! */
125 };
126 
127 /*
128  * Only the "context" field should be accessed outside this module.
129  * We keep the rest of an allocated chunk's header marked NOACCESS when using
130  * valgrind.  But note that freed chunk headers are kept accessible, for
131  * simplicity.
132  */
133 #define GENERATIONCHUNK_PRIVATE_LEN	offsetof(GenerationChunk, context)
134 
135 /*
136  * GenerationIsValid
137  *		True iff set is valid allocation set.
138  */
139 #define GenerationIsValid(set) PointerIsValid(set)
140 
141 #define GenerationPointerGetChunk(ptr) \
142 	((GenerationChunk *)(((char *)(ptr)) - Generation_CHUNKHDRSZ))
143 #define GenerationChunkGetPointer(chk) \
144 	((GenerationPointer *)(((char *)(chk)) + Generation_CHUNKHDRSZ))
145 
146 /*
147  * These functions implement the MemoryContext API for Generation contexts.
148  */
149 static void *GenerationAlloc(MemoryContext context, Size size);
150 static void GenerationFree(MemoryContext context, void *pointer);
151 static void *GenerationRealloc(MemoryContext context, void *pointer, Size size);
152 static void GenerationReset(MemoryContext context);
153 static void GenerationDelete(MemoryContext context);
154 static Size GenerationGetChunkSpace(MemoryContext context, void *pointer);
155 static bool GenerationIsEmpty(MemoryContext context);
156 static void GenerationStats(MemoryContext context,
157 				MemoryStatsPrintFunc printfunc, void *passthru,
158 				MemoryContextCounters *totals);
159 
160 #ifdef MEMORY_CONTEXT_CHECKING
161 static void GenerationCheck(MemoryContext context);
162 #endif
163 
164 /*
165  * This is the virtual function table for Generation contexts.
166  */
167 static const MemoryContextMethods GenerationMethods = {
168 	GenerationAlloc,
169 	GenerationFree,
170 	GenerationRealloc,
171 	GenerationReset,
172 	GenerationDelete,
173 	GenerationGetChunkSpace,
174 	GenerationIsEmpty,
175 	GenerationStats
176 #ifdef MEMORY_CONTEXT_CHECKING
177 	,GenerationCheck
178 #endif
179 };
180 
181 /* ----------
182  * Debug macros
183  * ----------
184  */
185 #ifdef HAVE_ALLOCINFO
186 #define GenerationFreeInfo(_cxt, _chunk) \
187 			fprintf(stderr, "GenerationFree: %s: %p, %lu\n", \
188 				(_cxt)->name, (_chunk), (_chunk)->size)
189 #define GenerationAllocInfo(_cxt, _chunk) \
190 			fprintf(stderr, "GenerationAlloc: %s: %p, %lu\n", \
191 				(_cxt)->name, (_chunk), (_chunk)->size)
192 #else
193 #define GenerationFreeInfo(_cxt, _chunk)
194 #define GenerationAllocInfo(_cxt, _chunk)
195 #endif
196 
197 
198 /*
199  * Public routines
200  */
201 
202 
203 /*
204  * GenerationContextCreate
205  *		Create a new Generation context.
206  *
207  * parent: parent context, or NULL if top-level context
208  * name: name of context (must be statically allocated)
209  * blockSize: generation block size
210  */
211 MemoryContext
GenerationContextCreate(MemoryContext parent,const char * name,Size blockSize)212 GenerationContextCreate(MemoryContext parent,
213 						const char *name,
214 						Size blockSize)
215 {
216 	GenerationContext *set;
217 
218 	/* Assert we padded GenerationChunk properly */
219 	StaticAssertStmt(Generation_CHUNKHDRSZ == MAXALIGN(Generation_CHUNKHDRSZ),
220 					 "sizeof(GenerationChunk) is not maxaligned");
221 	StaticAssertStmt(offsetof(GenerationChunk, context) + sizeof(MemoryContext) ==
222 					 Generation_CHUNKHDRSZ,
223 					 "padding calculation in GenerationChunk is wrong");
224 
225 	/*
226 	 * First, validate allocation parameters.  (If we're going to throw an
227 	 * error, we should do so before the context is created, not after.)  We
228 	 * somewhat arbitrarily enforce a minimum 1K block size, mostly because
229 	 * that's what AllocSet does.
230 	 */
231 	if (blockSize != MAXALIGN(blockSize) ||
232 		blockSize < 1024 ||
233 		!AllocHugeSizeIsValid(blockSize))
234 		elog(ERROR, "invalid blockSize for memory context: %zu",
235 			 blockSize);
236 
237 	/*
238 	 * Allocate the context header.  Unlike aset.c, we never try to combine
239 	 * this with the first regular block, since that would prevent us from
240 	 * freeing the first generation of allocations.
241 	 */
242 
243 	set = (GenerationContext *) malloc(MAXALIGN(sizeof(GenerationContext)));
244 	if (set == NULL)
245 	{
246 		MemoryContextStats(TopMemoryContext);
247 		ereport(ERROR,
248 				(errcode(ERRCODE_OUT_OF_MEMORY),
249 				 errmsg("out of memory"),
250 				 errdetail("Failed while creating memory context \"%s\".",
251 						   name)));
252 	}
253 
254 	/*
255 	 * Avoid writing code that can fail between here and MemoryContextCreate;
256 	 * we'd leak the header if we ereport in this stretch.
257 	 */
258 
259 	/* Fill in GenerationContext-specific header fields */
260 	set->blockSize = blockSize;
261 	set->block = NULL;
262 	dlist_init(&set->blocks);
263 
264 	/* Finally, do the type-independent part of context creation */
265 	MemoryContextCreate((MemoryContext) set,
266 						T_GenerationContext,
267 						&GenerationMethods,
268 						parent,
269 						name);
270 
271 	return (MemoryContext) set;
272 }
273 
274 /*
275  * GenerationReset
276  *		Frees all memory which is allocated in the given set.
277  *
278  * The code simply frees all the blocks in the context - we don't keep any
279  * keeper blocks or anything like that.
280  */
281 static void
GenerationReset(MemoryContext context)282 GenerationReset(MemoryContext context)
283 {
284 	GenerationContext *set = (GenerationContext *) context;
285 	dlist_mutable_iter miter;
286 
287 	AssertArg(GenerationIsValid(set));
288 
289 #ifdef MEMORY_CONTEXT_CHECKING
290 	/* Check for corruption and leaks before freeing */
291 	GenerationCheck(context);
292 #endif
293 
294 	dlist_foreach_modify(miter, &set->blocks)
295 	{
296 		GenerationBlock *block = dlist_container(GenerationBlock, node, miter.cur);
297 
298 		dlist_delete(miter.cur);
299 
300 #ifdef CLOBBER_FREED_MEMORY
301 		wipe_mem(block, block->blksize);
302 #endif
303 
304 		free(block);
305 	}
306 
307 	set->block = NULL;
308 
309 	Assert(dlist_is_empty(&set->blocks));
310 }
311 
312 /*
313  * GenerationDelete
314  *		Free all memory which is allocated in the given context.
315  */
316 static void
GenerationDelete(MemoryContext context)317 GenerationDelete(MemoryContext context)
318 {
319 	/* Reset to release all the GenerationBlocks */
320 	GenerationReset(context);
321 	/* And free the context header */
322 	free(context);
323 }
324 
325 /*
326  * GenerationAlloc
327  *		Returns pointer to allocated memory of given size or NULL if
328  *		request could not be completed; memory is added to the set.
329  *
330  * No request may exceed:
331  *		MAXALIGN_DOWN(SIZE_MAX) - Generation_BLOCKHDRSZ - Generation_CHUNKHDRSZ
332  * All callers use a much-lower limit.
333  *
334  * Note: when using valgrind, it doesn't matter how the returned allocation
335  * is marked, as mcxt.c will set it to UNDEFINED.  In some paths we will
336  * return space that is marked NOACCESS - GenerationRealloc has to beware!
337  */
338 static void *
GenerationAlloc(MemoryContext context,Size size)339 GenerationAlloc(MemoryContext context, Size size)
340 {
341 	GenerationContext *set = (GenerationContext *) context;
342 	GenerationBlock *block;
343 	GenerationChunk *chunk;
344 	Size		chunk_size = MAXALIGN(size);
345 
346 	/* is it an over-sized chunk? if yes, allocate special block */
347 	if (chunk_size > set->blockSize / 8)
348 	{
349 		Size		blksize = chunk_size + Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ;
350 
351 		block = (GenerationBlock *) malloc(blksize);
352 		if (block == NULL)
353 			return NULL;
354 
355 		/* block with a single (used) chunk */
356 		block->blksize = blksize;
357 		block->nchunks = 1;
358 		block->nfree = 0;
359 
360 		/* the block is completely full */
361 		block->freeptr = block->endptr = ((char *) block) + blksize;
362 
363 		chunk = (GenerationChunk *) (((char *) block) + Generation_BLOCKHDRSZ);
364 		chunk->block = block;
365 		chunk->context = set;
366 		chunk->size = chunk_size;
367 
368 #ifdef MEMORY_CONTEXT_CHECKING
369 		chunk->requested_size = size;
370 		/* set mark to catch clobber of "unused" space */
371 		if (size < chunk_size)
372 			set_sentinel(GenerationChunkGetPointer(chunk), size);
373 #endif
374 #ifdef RANDOMIZE_ALLOCATED_MEMORY
375 		/* fill the allocated space with junk */
376 		randomize_mem((char *) GenerationChunkGetPointer(chunk), size);
377 #endif
378 
379 		/* add the block to the list of allocated blocks */
380 		dlist_push_head(&set->blocks, &block->node);
381 
382 		GenerationAllocInfo(set, chunk);
383 
384 		/* Ensure any padding bytes are marked NOACCESS. */
385 		VALGRIND_MAKE_MEM_NOACCESS((char *) GenerationChunkGetPointer(chunk) + size,
386 								   chunk_size - size);
387 
388 		/* Disallow external access to private part of chunk header. */
389 		VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
390 
391 		return GenerationChunkGetPointer(chunk);
392 	}
393 
394 	/*
395 	 * Not an over-sized chunk. Is there enough space in the current block? If
396 	 * not, allocate a new "regular" block.
397 	 */
398 	block = set->block;
399 
400 	if ((block == NULL) ||
401 		(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
402 	{
403 		Size		blksize = set->blockSize;
404 
405 		block = (GenerationBlock *) malloc(blksize);
406 
407 		if (block == NULL)
408 			return NULL;
409 
410 		block->blksize = blksize;
411 		block->nchunks = 0;
412 		block->nfree = 0;
413 
414 		block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
415 		block->endptr = ((char *) block) + blksize;
416 
417 		/* Mark unallocated space NOACCESS. */
418 		VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
419 								   blksize - Generation_BLOCKHDRSZ);
420 
421 		/* add it to the doubly-linked list of blocks */
422 		dlist_push_head(&set->blocks, &block->node);
423 
424 		/* and also use it as the current allocation block */
425 		set->block = block;
426 	}
427 
428 	/* we're supposed to have a block with enough free space now */
429 	Assert(block != NULL);
430 	Assert((block->endptr - block->freeptr) >= Generation_CHUNKHDRSZ + chunk_size);
431 
432 	chunk = (GenerationChunk *) block->freeptr;
433 
434 	/* Prepare to initialize the chunk header. */
435 	VALGRIND_MAKE_MEM_UNDEFINED(chunk, Generation_CHUNKHDRSZ);
436 
437 	block->nchunks += 1;
438 	block->freeptr += (Generation_CHUNKHDRSZ + chunk_size);
439 
440 	Assert(block->freeptr <= block->endptr);
441 
442 	chunk->block = block;
443 	chunk->context = set;
444 	chunk->size = chunk_size;
445 
446 #ifdef MEMORY_CONTEXT_CHECKING
447 	chunk->requested_size = size;
448 	/* set mark to catch clobber of "unused" space */
449 	if (size < chunk->size)
450 		set_sentinel(GenerationChunkGetPointer(chunk), size);
451 #endif
452 #ifdef RANDOMIZE_ALLOCATED_MEMORY
453 	/* fill the allocated space with junk */
454 	randomize_mem((char *) GenerationChunkGetPointer(chunk), size);
455 #endif
456 
457 	GenerationAllocInfo(set, chunk);
458 
459 	/* Ensure any padding bytes are marked NOACCESS. */
460 	VALGRIND_MAKE_MEM_NOACCESS((char *) GenerationChunkGetPointer(chunk) + size,
461 							   chunk_size - size);
462 
463 	/* Disallow external access to private part of chunk header. */
464 	VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
465 
466 	return GenerationChunkGetPointer(chunk);
467 }
468 
469 /*
470  * GenerationFree
471  *		Update number of chunks in the block, and if all chunks in the block
472  *		are now free then discard the block.
473  */
474 static void
GenerationFree(MemoryContext context,void * pointer)475 GenerationFree(MemoryContext context, void *pointer)
476 {
477 	GenerationContext *set = (GenerationContext *) context;
478 	GenerationChunk *chunk = GenerationPointerGetChunk(pointer);
479 	GenerationBlock *block;
480 
481 	/* Allow access to private part of chunk header. */
482 	VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN);
483 
484 	block = chunk->block;
485 
486 #ifdef MEMORY_CONTEXT_CHECKING
487 	/* Test for someone scribbling on unused space in chunk */
488 	if (chunk->requested_size < chunk->size)
489 		if (!sentinel_ok(pointer, chunk->requested_size))
490 			elog(WARNING, "detected write past chunk end in %s %p",
491 				 ((MemoryContext) set)->name, chunk);
492 #endif
493 
494 #ifdef CLOBBER_FREED_MEMORY
495 	wipe_mem(pointer, chunk->size);
496 #endif
497 
498 	/* Reset context to NULL in freed chunks */
499 	chunk->context = NULL;
500 
501 #ifdef MEMORY_CONTEXT_CHECKING
502 	/* Reset requested_size to 0 in freed chunks */
503 	chunk->requested_size = 0;
504 #endif
505 
506 	block->nfree += 1;
507 
508 	Assert(block->nchunks > 0);
509 	Assert(block->nfree <= block->nchunks);
510 
511 	/* If there are still allocated chunks in the block, we're done. */
512 	if (block->nfree < block->nchunks)
513 		return;
514 
515 	/*
516 	 * The block is empty, so let's get rid of it. First remove it from the
517 	 * list of blocks, then return it to malloc().
518 	 */
519 	dlist_delete(&block->node);
520 
521 	/* Also make sure the block is not marked as the current block. */
522 	if (set->block == block)
523 		set->block = NULL;
524 
525 	free(block);
526 }
527 
528 /*
529  * GenerationRealloc
530  *		When handling repalloc, we simply allocate a new chunk, copy the data
531  *		and discard the old one. The only exception is when the new size fits
532  *		into the old chunk - in that case we just update chunk header.
533  */
534 static void *
GenerationRealloc(MemoryContext context,void * pointer,Size size)535 GenerationRealloc(MemoryContext context, void *pointer, Size size)
536 {
537 	GenerationContext *set = (GenerationContext *) context;
538 	GenerationChunk *chunk = GenerationPointerGetChunk(pointer);
539 	GenerationPointer newPointer;
540 	Size		oldsize;
541 
542 	/* Allow access to private part of chunk header. */
543 	VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN);
544 
545 	oldsize = chunk->size;
546 
547 #ifdef MEMORY_CONTEXT_CHECKING
548 	/* Test for someone scribbling on unused space in chunk */
549 	if (chunk->requested_size < oldsize)
550 		if (!sentinel_ok(pointer, chunk->requested_size))
551 			elog(WARNING, "detected write past chunk end in %s %p",
552 				 ((MemoryContext) set)->name, chunk);
553 #endif
554 
555 	/*
556 	 * Maybe the allocated area already is >= the new size.  (In particular,
557 	 * we always fall out here if the requested size is a decrease.)
558 	 *
559 	 * This memory context does not use power-of-2 chunk sizing and instead
560 	 * carves the chunks to be as small as possible, so most repalloc() calls
561 	 * will end up in the palloc/memcpy/pfree branch.
562 	 *
563 	 * XXX Perhaps we should annotate this condition with unlikely()?
564 	 */
565 	if (oldsize >= size)
566 	{
567 #ifdef MEMORY_CONTEXT_CHECKING
568 		Size		oldrequest = chunk->requested_size;
569 
570 #ifdef RANDOMIZE_ALLOCATED_MEMORY
571 		/* We can only fill the extra space if we know the prior request */
572 		if (size > oldrequest)
573 			randomize_mem((char *) pointer + oldrequest,
574 						  size - oldrequest);
575 #endif
576 
577 		chunk->requested_size = size;
578 
579 		/*
580 		 * If this is an increase, mark any newly-available part UNDEFINED.
581 		 * Otherwise, mark the obsolete part NOACCESS.
582 		 */
583 		if (size > oldrequest)
584 			VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + oldrequest,
585 										size - oldrequest);
586 		else
587 			VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size,
588 									   oldsize - size);
589 
590 		/* set mark to catch clobber of "unused" space */
591 		if (size < oldsize)
592 			set_sentinel(pointer, size);
593 #else							/* !MEMORY_CONTEXT_CHECKING */
594 
595 		/*
596 		 * We don't have the information to determine whether we're growing
597 		 * the old request or shrinking it, so we conservatively mark the
598 		 * entire new allocation DEFINED.
599 		 */
600 		VALGRIND_MAKE_MEM_NOACCESS(pointer, oldsize);
601 		VALGRIND_MAKE_MEM_DEFINED(pointer, size);
602 #endif
603 
604 		/* Disallow external access to private part of chunk header. */
605 		VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
606 
607 		return pointer;
608 	}
609 
610 	/* allocate new chunk */
611 	newPointer = GenerationAlloc((MemoryContext) set, size);
612 
613 	/* leave immediately if request was not completed */
614 	if (newPointer == NULL)
615 	{
616 		/* Disallow external access to private part of chunk header. */
617 		VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
618 		return NULL;
619 	}
620 
621 	/*
622 	 * GenerationAlloc() may have returned a region that is still NOACCESS.
623 	 * Change it to UNDEFINED for the moment; memcpy() will then transfer
624 	 * definedness from the old allocation to the new.  If we know the old
625 	 * allocation, copy just that much.  Otherwise, make the entire old chunk
626 	 * defined to avoid errors as we copy the currently-NOACCESS trailing
627 	 * bytes.
628 	 */
629 	VALGRIND_MAKE_MEM_UNDEFINED(newPointer, size);
630 #ifdef MEMORY_CONTEXT_CHECKING
631 	oldsize = chunk->requested_size;
632 #else
633 	VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize);
634 #endif
635 
636 	/* transfer existing data (certain to fit) */
637 	memcpy(newPointer, pointer, oldsize);
638 
639 	/* free old chunk */
640 	GenerationFree((MemoryContext) set, pointer);
641 
642 	return newPointer;
643 }
644 
645 /*
646  * GenerationGetChunkSpace
647  *		Given a currently-allocated chunk, determine the total space
648  *		it occupies (including all memory-allocation overhead).
649  */
650 static Size
GenerationGetChunkSpace(MemoryContext context,void * pointer)651 GenerationGetChunkSpace(MemoryContext context, void *pointer)
652 {
653 	GenerationChunk *chunk = GenerationPointerGetChunk(pointer);
654 	Size		result;
655 
656 	VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN);
657 	result = chunk->size + Generation_CHUNKHDRSZ;
658 	VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
659 	return result;
660 }
661 
662 /*
663  * GenerationIsEmpty
664  *		Is a GenerationContext empty of any allocated space?
665  */
666 static bool
GenerationIsEmpty(MemoryContext context)667 GenerationIsEmpty(MemoryContext context)
668 {
669 	GenerationContext *set = (GenerationContext *) context;
670 
671 	return dlist_is_empty(&set->blocks);
672 }
673 
674 /*
675  * GenerationStats
676  *		Compute stats about memory consumption of a Generation context.
677  *
678  * printfunc: if not NULL, pass a human-readable stats string to this.
679  * passthru: pass this pointer through to printfunc.
680  * totals: if not NULL, add stats about this context into *totals.
681  *
682  * XXX freespace only accounts for empty space at the end of the block, not
683  * space of freed chunks (which is unknown).
684  */
685 static void
GenerationStats(MemoryContext context,MemoryStatsPrintFunc printfunc,void * passthru,MemoryContextCounters * totals)686 GenerationStats(MemoryContext context,
687 				MemoryStatsPrintFunc printfunc, void *passthru,
688 				MemoryContextCounters *totals)
689 {
690 	GenerationContext *set = (GenerationContext *) context;
691 	Size		nblocks = 0;
692 	Size		nchunks = 0;
693 	Size		nfreechunks = 0;
694 	Size		totalspace;
695 	Size		freespace = 0;
696 	dlist_iter	iter;
697 
698 	/* Include context header in totalspace */
699 	totalspace = MAXALIGN(sizeof(GenerationContext));
700 
701 	dlist_foreach(iter, &set->blocks)
702 	{
703 		GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
704 
705 		nblocks++;
706 		nchunks += block->nchunks;
707 		nfreechunks += block->nfree;
708 		totalspace += block->blksize;
709 		freespace += (block->endptr - block->freeptr);
710 	}
711 
712 	if (printfunc)
713 	{
714 		char		stats_string[200];
715 
716 		snprintf(stats_string, sizeof(stats_string),
717 				 "%zu total in %zd blocks (%zd chunks); %zu free (%zd chunks); %zu used",
718 				 totalspace, nblocks, nchunks, freespace,
719 				 nfreechunks, totalspace - freespace);
720 		printfunc(context, passthru, stats_string);
721 	}
722 
723 	if (totals)
724 	{
725 		totals->nblocks += nblocks;
726 		totals->freechunks += nfreechunks;
727 		totals->totalspace += totalspace;
728 		totals->freespace += freespace;
729 	}
730 }
731 
732 
733 #ifdef MEMORY_CONTEXT_CHECKING
734 
735 /*
736  * GenerationCheck
737  *		Walk through chunks and check consistency of memory.
738  *
739  * NOTE: report errors as WARNING, *not* ERROR or FATAL.  Otherwise you'll
740  * find yourself in an infinite loop when trouble occurs, because this
741  * routine will be entered again when elog cleanup tries to release memory!
742  */
743 static void
GenerationCheck(MemoryContext context)744 GenerationCheck(MemoryContext context)
745 {
746 	GenerationContext *gen = (GenerationContext *) context;
747 	const char *name = context->name;
748 	dlist_iter	iter;
749 
750 	/* walk all blocks in this context */
751 	dlist_foreach(iter, &gen->blocks)
752 	{
753 		GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
754 		int			nfree,
755 					nchunks;
756 		char	   *ptr;
757 
758 		/*
759 		 * nfree > nchunks is surely wrong, and we don't expect to see
760 		 * equality either, because such a block should have gotten freed.
761 		 */
762 		if (block->nfree >= block->nchunks)
763 			elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
764 				 name, block->nfree, block, block->nchunks);
765 
766 		/* Now walk through the chunks and count them. */
767 		nfree = 0;
768 		nchunks = 0;
769 		ptr = ((char *) block) + Generation_BLOCKHDRSZ;
770 
771 		while (ptr < block->freeptr)
772 		{
773 			GenerationChunk *chunk = (GenerationChunk *) ptr;
774 
775 			/* Allow access to private part of chunk header. */
776 			VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN);
777 
778 			/* move to the next chunk */
779 			ptr += (chunk->size + Generation_CHUNKHDRSZ);
780 
781 			nchunks += 1;
782 
783 			/* chunks have both block and context pointers, so check both */
784 			if (chunk->block != block)
785 				elog(WARNING, "problem in Generation %s: bogus block link in block %p, chunk %p",
786 					 name, block, chunk);
787 
788 			/*
789 			 * Check for valid context pointer.  Note this is an incomplete
790 			 * test, since palloc(0) produces an allocated chunk with
791 			 * requested_size == 0.
792 			 */
793 			if ((chunk->requested_size > 0 && chunk->context != gen) ||
794 				(chunk->context != gen && chunk->context != NULL))
795 				elog(WARNING, "problem in Generation %s: bogus context link in block %p, chunk %p",
796 					 name, block, chunk);
797 
798 			/* now make sure the chunk size is correct */
799 			if (chunk->size < chunk->requested_size ||
800 				chunk->size != MAXALIGN(chunk->size))
801 				elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p",
802 					 name, block, chunk);
803 
804 			/* is chunk allocated? */
805 			if (chunk->context != NULL)
806 			{
807 				/* check sentinel, but only in allocated blocks */
808 				if (chunk->requested_size < chunk->size &&
809 					!sentinel_ok(chunk, Generation_CHUNKHDRSZ + chunk->requested_size))
810 					elog(WARNING, "problem in Generation %s: detected write past chunk end in block %p, chunk %p",
811 						 name, block, chunk);
812 			}
813 			else
814 				nfree += 1;
815 
816 			/*
817 			 * If chunk is allocated, disallow external access to private part
818 			 * of chunk header.
819 			 */
820 			if (chunk->context != NULL)
821 				VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
822 		}
823 
824 		/*
825 		 * Make sure we got the expected number of allocated and free chunks
826 		 * (as tracked in the block header).
827 		 */
828 		if (nchunks != block->nchunks)
829 			elog(WARNING, "problem in Generation %s: number of allocated chunks %d in block %p does not match header %d",
830 				 name, nchunks, block, block->nchunks);
831 
832 		if (nfree != block->nfree)
833 			elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d",
834 				 name, nfree, block, block->nfree);
835 	}
836 }
837 
838 #endif							/* MEMORY_CONTEXT_CHECKING */
839