Make the internal allocator fast.
[mono.git] / mono / metadata / sgen-internal.c
1 /*
2  * sgen-gc.c: Simple generational GC.
3  *
4  * Author:
5  *      Paolo Molaro (lupus@ximian.com)
6  *
7  * Copyright 2005-2010 Novell, Inc (http://www.novell.com)
8  *
9  * Thread start/stop adapted from Boehm's GC:
10  * Copyright (c) 1994 by Xerox Corporation.  All rights reserved.
11  * Copyright (c) 1996 by Silicon Graphics.  All rights reserved.
12  * Copyright (c) 1998 by Fergus Henderson.  All rights reserved.
13  * Copyright (c) 2000-2004 by Hewlett-Packard Company.  All rights reserved.
14  *
15  * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
16  * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
17  *
18  * Permission is hereby granted to use or copy this program
19  * for any purpose,  provided the above notices are retained on all copies.
20  * Permission to modify the code and to distribute modified code is granted,
21  * provided the above notices are retained, and a notice that the code was
22  * modified is included with the above copyright notice.
23  *
24  *
25  * Copyright 2001-2003 Ximian, Inc
26  * Copyright 2003-2010 Novell, Inc.
27  * 
28  * Permission is hereby granted, free of charge, to any person obtaining
29  * a copy of this software and associated documentation files (the
30  * "Software"), to deal in the Software without restriction, including
31  * without limitation the rights to use, copy, modify, merge, publish,
32  * distribute, sublicense, and/or sell copies of the Software, and to
33  * permit persons to whom the Software is furnished to do so, subject to
34  * the following conditions:
35  * 
36  * The above copyright notice and this permission notice shall be
37  * included in all copies or substantial portions of the Software.
38  * 
39  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
40  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
41  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
42  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
43  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
44  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
45  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
46  */
47
48 #include "metadata/sgen-gc.h"
49
50 /* Pinned objects are allocated in the LOS space if bigger than half a page
51  * or from freelists otherwise. We assume that pinned objects are relatively few
52  * and they have a slow dying speed (like interned strings, thread objects).
53  * As such they will be collected only at major collections.
54  * free lists are not global: when we need memory we allocate a PinnedChunk.
55  * Each pinned chunk is made of several pages, the first of wich is used
56  * internally for bookeeping (here think of a page as 4KB). The bookeeping
57  * includes the freelists vectors and info about the object size of each page
58  * in the pinned chunk. So, when needed, a free page is found in a pinned chunk,
59  * a size is assigned to it, the page is divided in the proper chunks and each
60  * chunk is added to the freelist. To not waste space, the remaining space in the
61  * first page is used as objects of size 16 or 32 (need to measure which are more
62  * common).
63  * We use this same structure to allocate memory used internally by the GC, so
64  * we never use malloc/free if we need to alloc during collection: the world is stopped
65  * and malloc/free will deadlock.
66  * When we want to iterate over pinned objects, we just scan a page at a time
67  * linearly according to the size of objects in the page: the next pointer used to link
68  * the items in the freelist uses the same word as the vtable. Since we keep freelists
69  * for each pinned chunk, if the word points outside the pinned chunk it means
70  * it is an object.
71  * We could avoid this expensive scanning in creative ways. We could have a policy
72  * of putting in the pinned space only objects we know about that have no struct fields
73  * with references and we can easily use a even expensive write barrier for them,
74  * since pointer writes on such objects should be rare.
75  * The best compromise is to just alloc interned strings and System.MonoType in them.
76  * It would be nice to allocate MonoThread in it, too: must check that we properly
77  * use write barriers so we don't have to do any expensive scanning of the whole pinned
78  * chunk list during minor collections. We can avoid it now because we alloc in it only
79  * reference-free objects.
80  */
81 struct _SgenPinnedChunk {
82         SgenBlock block;
83         int num_pages;
84         int *page_sizes; /* a 0 means the page is still unused */
85         void **free_list;
86         SgenPinnedChunk *free_list_nexts [SGEN_INTERNAL_FREELIST_NUM_SLOTS];
87         void *start_data;
88         void *data [1]; /* page sizes and free lists are stored here */
89 };
90
91 #define PINNED_FIRST_SLOT_SIZE (sizeof (gpointer) * 4)
92 #define MAX_FREELIST_SIZE 8192
93
94 /* This is a fixed value used for pinned chunks, not the system pagesize */
95 #define FREELIST_PAGESIZE (16*1024)
96
97 /* keep each size a multiple of ALLOC_ALIGN */
98 /* on 64 bit systems 8 is likely completely unused. */
99 static const int freelist_sizes [] = {
100            8,   16,   24,   32,   40,   48,   64,   80,
101           96,  128,  160,  192,  224,  256,  320,  384,
102          448,  512,  584,  680,  816, 1024, 1360, 2048,
103         2336, 2728, 3272, 4096, 5456, 8192 };
104
105 /*
106  * Slot indexes for the fixed INTERNAL_MEM_XXX types.  -1 if that type
107  * is dynamic.
108  */
109 static int fixed_type_freelist_slots [INTERNAL_MEM_MAX];
110
111 static SgenInternalAllocator unmanaged_allocator;
112
113 #define LARGE_INTERNAL_MEM_HEADER_MAGIC 0x7d289f3a
114
115 typedef struct _LargeInternalMemHeader LargeInternalMemHeader;
116 struct _LargeInternalMemHeader {
117         guint32 magic;
118         size_t size;
119         double data[0];
120 };
121
122 #ifdef SGEN_PARALLEL_MARK
123 static LOCK_DECLARE (internal_allocator_mutex);
124 #define LOCK_INTERNAL_ALLOCATOR pthread_mutex_lock (&internal_allocator_mutex)
125 #define UNLOCK_INTERNAL_ALLOCATOR pthread_mutex_unlock (&internal_allocator_mutex)
126 #else
127 #define LOCK_INTERNAL_ALLOCATOR
128 #define UNLOCK_INTERNAL_ALLOCATOR
129 #endif
130
131 static long long pinned_chunk_bytes_alloced = 0;
132 static long long large_internal_bytes_alloced = 0;
133
134 #ifdef HEAVY_STATISTICS
135 static long long stat_internal_alloc = 0;
136 #endif
137
138 /*
139  * Debug reporting.
140  */
141 static void
142 report_pinned_chunk (SgenPinnedChunk *chunk, int seq) {
143         void **p;
144         int i, free_pages, num_free, free_mem;
145         free_pages = 0;
146         for (i = 0; i < chunk->num_pages; ++i) {
147                 if (!chunk->page_sizes [i])
148                         free_pages++;
149         }
150         printf ("Pinned chunk %d at %p, size: %d, pages: %d, free: %d\n", seq, chunk, chunk->num_pages * FREELIST_PAGESIZE, chunk->num_pages, free_pages);
151         free_mem = FREELIST_PAGESIZE * free_pages;
152         for (i = 0; i < SGEN_INTERNAL_FREELIST_NUM_SLOTS; ++i) {
153                 if (!chunk->free_list [i])
154                         continue;
155                 num_free = 0;
156                 p = chunk->free_list [i];
157                 while (p) {
158                         num_free++;
159                         p = *p;
160                 }
161                 printf ("\tfree list of size %d, %d items\n", freelist_sizes [i], num_free);
162                 free_mem += freelist_sizes [i] * num_free;
163         }
164         printf ("\tfree memory in chunk: %d\n", free_mem);
165 }
166
167 /*
168  * Debug reporting.
169  */
170 void
171 mono_sgen_report_internal_mem_usage_full (SgenInternalAllocator *alc)
172 {
173         SgenPinnedChunk *chunk;
174         int i = 0;
175         for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next)
176                 report_pinned_chunk (chunk, i++);
177 }
178
179 void
180 mono_sgen_report_internal_mem_usage (void)
181 {
182         mono_sgen_report_internal_mem_usage_full (&unmanaged_allocator);
183 }
184
185 /*
186  * Find the slot number in the freelist for memory chunks that
187  * can contain @size objects.
188  */
189 static int
190 slot_for_size (size_t size)
191 {
192         int slot;
193         /* do a binary search or lookup table later. */
194         for (slot = 0; slot < SGEN_INTERNAL_FREELIST_NUM_SLOTS; ++slot) {
195                 if (freelist_sizes [slot] >= size)
196                         return slot;
197         }
198         g_assert_not_reached ();
199         return -1;
200 }
201
202 void
203 mono_sgen_register_fixed_internal_mem_type (int type, size_t size)
204 {
205         int slot;
206
207         g_assert (type >= 0 && type < INTERNAL_MEM_MAX);
208         g_assert (fixed_type_freelist_slots [type] == -1);
209
210         slot = slot_for_size (size);
211         g_assert (slot >= 0);
212
213         fixed_type_freelist_slots [type] = slot;
214 }
215
216 /*
217  * Build a free list for @size memory chunks from the memory area between
218  * start_page and end_page.
219  */
220 static void
221 build_freelist (SgenInternalAllocator *alc, SgenPinnedChunk *chunk, int slot, int size, char *start_page, char *end_page)
222 {
223         void **p, **end;
224         int count = 0;
225         /*g_print ("building freelist for slot %d, size %d in %p\n", slot, size, chunk);*/
226         p = (void**)start_page;
227         end = (void**)(end_page - size);
228         g_assert (!chunk->free_list [slot]);
229         chunk->free_list [slot] = p;
230         while ((char*)p + size <= (char*)end) {
231                 count++;
232                 *p = (void*)((char*)p + size);
233                 p = *p;
234         }
235         *p = NULL;
236         /*g_print ("%d items created, max: %d\n", count, (end_page - start_page) / size);*/
237
238         g_assert (!chunk->free_list_nexts [slot]);
239         chunk->free_list_nexts [slot] = alc->free_lists [slot];
240         alc->free_lists [slot] = chunk;
241 }
242
243 /* LOCKING: if !managed, requires the internal allocator lock to be held */
244 static SgenPinnedChunk*
245 alloc_pinned_chunk (SgenInternalAllocator *alc, gboolean managed)
246 {
247         SgenPinnedChunk *chunk;
248         int offset;
249         int size = SGEN_PINNED_CHUNK_SIZE;
250
251         if (managed)
252                 LOCK_INTERNAL_ALLOCATOR;
253
254         chunk = mono_sgen_alloc_os_memory_aligned (size, size, TRUE);
255         chunk->block.role = managed ? MEMORY_ROLE_PINNED : MEMORY_ROLE_INTERNAL;
256
257         if (managed)
258                 mono_sgen_update_heap_boundaries ((mword)chunk, ((mword)chunk + size));
259
260         pinned_chunk_bytes_alloced += size;
261
262         /* setup the bookeeping fields */
263         chunk->num_pages = size / FREELIST_PAGESIZE;
264         offset = G_STRUCT_OFFSET (SgenPinnedChunk, data);
265         chunk->page_sizes = (void*)((char*)chunk + offset);
266         offset += sizeof (int) * chunk->num_pages;
267         offset = SGEN_ALIGN_UP (offset);
268         chunk->free_list = (void*)((char*)chunk + offset);
269         offset += sizeof (void*) * SGEN_INTERNAL_FREELIST_NUM_SLOTS;
270         offset = SGEN_ALIGN_UP (offset);
271         chunk->start_data = (void*)((char*)chunk + offset);
272
273         /* allocate the first page to the freelist */
274         chunk->page_sizes [0] = PINNED_FIRST_SLOT_SIZE;
275         build_freelist (alc, chunk, slot_for_size (PINNED_FIRST_SLOT_SIZE), PINNED_FIRST_SLOT_SIZE,
276                         chunk->start_data, ((char*)chunk + FREELIST_PAGESIZE));
277         mono_sgen_debug_printf (4, "Allocated pinned chunk %p, size: %d\n", chunk, size);
278
279         chunk->block.next = alc->chunk_list;
280         alc->chunk_list = chunk;
281
282         if (managed)
283                 UNLOCK_INTERNAL_ALLOCATOR;
284
285         return chunk;
286 }
287
288 /* Must be called with an empty freelist for the given slot. */
289 static gboolean
290 populate_chunk_page (SgenInternalAllocator *alc, SgenPinnedChunk *chunk, int slot)
291 {
292         int size = freelist_sizes [slot];
293         int i;
294         g_assert (!chunk->free_list [slot]);
295         g_assert (!chunk->free_list_nexts [slot]);
296         for (i = 0; i < chunk->num_pages; ++i) {
297                 if (chunk->page_sizes [i])
298                         continue;
299                 chunk->page_sizes [i] = size;
300                 build_freelist (alc, chunk, slot, size, (char*)chunk + FREELIST_PAGESIZE * i, (char*)chunk + FREELIST_PAGESIZE * (i + 1));
301                 return TRUE;
302         }
303         return FALSE;
304 }
305
306 /* LOCKING: assumes the internal allocator lock is held */
307 static void*
308 alloc_from_slot (SgenInternalAllocator *alc, int slot, int type)
309 {
310         SgenPinnedChunk *pchunk;
311         size_t size = freelist_sizes [slot];
312
313         alc->small_internal_mem_bytes [type] += size;
314
315  restart:
316         pchunk = alc->free_lists [slot];
317         if (pchunk) {
318                 void **p = pchunk->free_list [slot];
319                 void *next;
320
321                 g_assert (p);
322
323                 next = *p;
324                 pchunk->free_list [slot] = next;
325
326                 if (!next) {
327                         alc->free_lists [slot] = pchunk->free_list_nexts [slot];
328                         pchunk->free_list_nexts [slot] = NULL;
329                 }
330
331                 memset (p, 0, size);
332                 return p;
333         }
334
335         for (pchunk = alc->chunk_list; pchunk; pchunk = pchunk->block.next) {
336                 if (populate_chunk_page (alc, pchunk, slot))
337                         goto restart;
338         }
339
340         pchunk = alloc_pinned_chunk (alc, type == INTERNAL_MEM_MANAGED);
341         /* FIXME: handle OOM */
342         if (pchunk->free_list [slot])
343                 goto restart;
344         if (!populate_chunk_page (alc, pchunk, slot))
345                 g_assert_not_reached ();
346         goto restart;
347 }
348
349 /* used for the GC-internal data structures */
350 void*
351 mono_sgen_alloc_internal_full (SgenInternalAllocator *alc, size_t size, int type)
352 {
353         int slot;
354         void *res = NULL;
355
356         g_assert (fixed_type_freelist_slots [type] == -1);
357
358         LOCK_INTERNAL_ALLOCATOR;
359
360         HEAVY_STAT (++stat_internal_alloc);
361
362         if (size > freelist_sizes [SGEN_INTERNAL_FREELIST_NUM_SLOTS - 1]) {
363                 LargeInternalMemHeader *mh;
364
365                 UNLOCK_INTERNAL_ALLOCATOR;
366
367                 size += sizeof (LargeInternalMemHeader);
368                 mh = mono_sgen_alloc_os_memory (size, TRUE);
369                 mh->magic = LARGE_INTERNAL_MEM_HEADER_MAGIC;
370                 mh->size = size;
371                 /* FIXME: do a CAS here */
372                 large_internal_bytes_alloced += size;
373                 return mh->data;
374         }
375
376         slot = slot_for_size (size);
377         g_assert (size <= freelist_sizes [slot]);
378         res = alloc_from_slot (alc, slot, type);
379
380         UNLOCK_INTERNAL_ALLOCATOR;
381
382         return res;
383 }
384
385 void*
386 mono_sgen_alloc_internal (int type)
387 {
388         void *res;
389         int slot = fixed_type_freelist_slots [type];
390         g_assert (slot >= 0);
391
392         LOCK_INTERNAL_ALLOCATOR;
393         res = alloc_from_slot (&unmanaged_allocator, slot, type);
394         UNLOCK_INTERNAL_ALLOCATOR;
395
396         return res;
397 }
398
399 void*
400 mono_sgen_alloc_internal_dynamic (size_t size, int type)
401 {
402         return mono_sgen_alloc_internal_full (&unmanaged_allocator, size, type);
403 }
404
405 static void
406 free_from_slot (SgenInternalAllocator *alc, void *addr, int slot, int type)
407 {
408         SgenPinnedChunk *pchunk = (SgenPinnedChunk*)SGEN_PINNED_CHUNK_FOR_PTR (addr);
409         void **p = addr;
410         void *next;
411
412         LOCK_INTERNAL_ALLOCATOR;
413
414         g_assert (addr >= (void*)pchunk && (char*)addr < (char*)pchunk + pchunk->num_pages * FREELIST_PAGESIZE);
415         if (type == INTERNAL_MEM_MANAGED)
416                 g_assert (pchunk->block.role == MEMORY_ROLE_PINNED);
417         else
418                 g_assert (pchunk->block.role == MEMORY_ROLE_INTERNAL);
419
420         next = pchunk->free_list [slot];
421         *p = next;
422         pchunk->free_list [slot] = p;
423
424         if (!next) {
425                 g_assert (!pchunk->free_list_nexts [slot]);
426                 pchunk->free_list_nexts [slot] = alc->free_lists [slot];
427                 alc->free_lists [slot] = pchunk;
428         }
429
430         alc->small_internal_mem_bytes [type] -= freelist_sizes [slot];
431
432         UNLOCK_INTERNAL_ALLOCATOR;
433 }
434
435 void
436 mono_sgen_free_internal_full (SgenInternalAllocator *alc, void *addr, size_t size, int type)
437 {
438         LargeInternalMemHeader *mh;
439
440         g_assert (fixed_type_freelist_slots [type] == -1);
441
442         if (!addr)
443                 return;
444
445         if (size <= freelist_sizes [SGEN_INTERNAL_FREELIST_NUM_SLOTS - 1]) {
446                 int slot = slot_for_size (size);
447                 free_from_slot (alc, addr, slot, type);
448                 return;
449         }
450
451         mh = (LargeInternalMemHeader*)((char*)addr - G_STRUCT_OFFSET (LargeInternalMemHeader, data));
452         g_assert (mh->magic == LARGE_INTERNAL_MEM_HEADER_MAGIC);
453         g_assert (mh->size == size + sizeof (LargeInternalMemHeader));
454         /* FIXME: do a CAS */
455         large_internal_bytes_alloced -= mh->size;
456         mono_sgen_free_os_memory (mh, mh->size);
457 }
458
459 void
460 mono_sgen_free_internal (void *addr, int type)
461 {
462         int slot = fixed_type_freelist_slots [type];
463         g_assert (slot >= 0);
464
465         free_from_slot (&unmanaged_allocator, addr, slot, type);
466 }
467
468 void
469 mono_sgen_free_internal_dynamic (void *addr, size_t size, int type)
470 {
471         mono_sgen_free_internal_full (&unmanaged_allocator, addr, size, type);
472 }
473
474 void
475 mono_sgen_dump_internal_mem_usage (FILE *heap_dump_file)
476 {
477         static char const *internal_mem_names [] = { "managed", "pin-queue", "fragment", "section", "scan-starts",
478                                                      "fin-table", "finalize-entry", "dislink-table",
479                                                      "dislink", "roots-table", "root-record", "statistics",
480                                                      "remset", "gray-queue", "store-remset", "marksweep-tables",
481                                                      "marksweep-block-info", "ephemeron-link" };
482
483         int i;
484
485         fprintf (heap_dump_file, "<other-mem-usage type=\"large-internal\" size=\"%lld\"/>\n", large_internal_bytes_alloced);
486         fprintf (heap_dump_file, "<other-mem-usage type=\"pinned-chunks\" size=\"%lld\"/>\n", pinned_chunk_bytes_alloced);
487         for (i = 0; i < INTERNAL_MEM_MAX; ++i) {
488                 fprintf (heap_dump_file, "<other-mem-usage type=\"%s\" size=\"%ld\"/>\n",
489                                 internal_mem_names [i], unmanaged_allocator.small_internal_mem_bytes [i]);
490         }
491 }
492
493 void
494 mono_sgen_init_internal_allocator (void)
495 {
496         int i;
497
498         g_assert (SGEN_INTERNAL_FREELIST_NUM_SLOTS == sizeof (freelist_sizes) / sizeof (freelist_sizes [0]));
499
500         for (i = 0; i < INTERNAL_MEM_MAX; ++i)
501                 fixed_type_freelist_slots [i] = -1;
502
503 #ifdef HEAVY_STATISTICS
504         mono_counters_register ("Internal allocs", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_internal_alloc);
505 #endif
506 }
507
508 void
509 mono_sgen_internal_scan_objects (SgenInternalAllocator *alc, IterateObjectCallbackFunc callback, void *callback_data)
510 {
511         SgenPinnedChunk *chunk;
512         int i, obj_size;
513         char *p, *endp;
514         void **ptr;
515         void *end_chunk;
516         for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next) {
517                 end_chunk = (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE;
518                 mono_sgen_debug_printf (6, "Scanning pinned chunk %p (range: %p-%p)\n", chunk, chunk->start_data, end_chunk);
519                 for (i = 0; i < chunk->num_pages; ++i) {
520                         obj_size = chunk->page_sizes [i];
521                         if (!obj_size)
522                                 continue;
523                         p = i? (char*)chunk + i * FREELIST_PAGESIZE: chunk->start_data;
524                         endp = i? p + FREELIST_PAGESIZE: (char*)chunk + FREELIST_PAGESIZE;
525                         mono_sgen_debug_printf (6, "Page %d (size: %d, range: %p-%p)\n", i, obj_size, p, endp);
526                         while (p + obj_size <= endp) {
527                                 ptr = (void**)p;
528                                 /* if the first word (the vtable) is outside the chunk we have an object */
529                                 if (*ptr && (*ptr < (void*)chunk || *ptr >= end_chunk))
530                                         callback ((char*)ptr, obj_size, callback_data);
531                                 p += obj_size;
532                         }
533                 }
534         }
535 }
536
537 /*
538  * the array of pointers from @start to @end contains conservative
539  * pointers to objects inside @chunk: mark each referenced object
540  * with the PIN bit.
541  */
542 static void
543 mark_pinned_from_addresses (SgenPinnedChunk *chunk, void **start, void **end, IterateObjectCallbackFunc callback, void *callback_data)
544 {
545         for (; start < end; start++) {
546                 char *addr = *start;
547                 int offset = (char*)addr - (char*)chunk;
548                 int page = offset / FREELIST_PAGESIZE;
549                 int obj_offset = page == 0? offset - ((char*)chunk->start_data - (char*)chunk): offset % FREELIST_PAGESIZE;
550                 int slot_size = chunk->page_sizes [page];
551                 void **ptr;
552                 /* the page is not allocated */
553                 if (!slot_size)
554                         continue;
555                 /* would be faster if we restrict the sizes to power of two,
556                  * but that's a waste of memory: need to measure. it could reduce
557                  * fragmentation since there are less pages needed, if for example
558                  * someone interns strings of each size we end up with one page per
559                  * interned string (still this is just ~40 KB): with more fine-grained sizes
560                  * this increases the number of used pages.
561                  */
562                 if (page == 0) {
563                         obj_offset /= slot_size;
564                         obj_offset *= slot_size;
565                         addr = (char*)chunk->start_data + obj_offset;
566                 } else {
567                         obj_offset /= slot_size;
568                         obj_offset *= slot_size;
569                         addr = (char*)chunk + page * FREELIST_PAGESIZE + obj_offset;
570                 }
571                 ptr = (void**)addr;
572                 /* if the vtable is inside the chunk it's on the freelist, so skip */
573                 /* FIXME: is it possible that we're pinning objects more than once here? */
574                 if (*ptr && (*ptr < (void*)chunk->start_data || *ptr > (void*)((char*)chunk + chunk->num_pages * FREELIST_PAGESIZE)))
575                         callback (addr, slot_size, callback_data);
576         }
577 }
578
579 void
580 mono_sgen_internal_scan_pinned_objects (SgenInternalAllocator *alc, IterateObjectCallbackFunc callback, void *callback_data)
581 {
582         SgenPinnedChunk *chunk;
583
584         /* look for pinned addresses for pinned-alloc objects */
585         mono_sgen_debug_printf (6, "Pinning from pinned-alloc objects\n");
586         for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next) {
587                 int num_pinned;
588                 void **pinned = mono_sgen_find_optimized_pin_queue_area (chunk->start_data,
589                                 (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE, &num_pinned);
590                 if (num_pinned)
591                         mark_pinned_from_addresses (chunk, pinned, pinned + num_pinned, callback, callback_data);
592         }
593 }