1 /*
2 * %CopyrightBegin%
3 *
4 * Copyright Ericsson AB 2002-2020. All Rights Reserved.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * %CopyrightEnd%
19 */
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #define ERL_WANT_GC_INTERNALS__
25
26 #include "sys.h"
27 #include "erl_vm.h"
28 #include "global.h"
29 #include "erl_process.h"
30 #include "erl_db.h"
31 #include "beam_catches.h"
32 #include "erl_binary.h"
33 #include "erl_bits.h"
34 #include "erl_map.h"
35 #include "error.h"
36 #include "big.h"
37 #include "erl_gc.h"
38 #include "dtrace-wrapper.h"
39 #include "erl_bif_unique.h"
40 #include "dist.h"
41 #include "erl_nfunc_sched.h"
42 #include "erl_proc_sig_queue.h"
43 #include "beam_common.h"
44
45 #define ERTS_INACT_WR_PB_LEAVE_MUCH_LIMIT 1
46 #define ERTS_INACT_WR_PB_LEAVE_MUCH_PERCENTAGE 20
47 #define ERTS_INACT_WR_PB_LEAVE_LIMIT 10
48 #define ERTS_INACT_WR_PB_LEAVE_PERCENTAGE 10
49
50 #if defined(DEBUG) || 0
51 #define ERTS_GC_DEBUG
52 #else
53 #undef ERTS_GC_DEBUG
54 #endif
55 #ifdef ERTS_GC_DEBUG
56 # define ERTS_GC_ASSERT ASSERT
57 #else
58 # define ERTS_GC_ASSERT(B) ((void) 1)
59 #endif
60
61 #if defined(DEBUG) && 0
62 # define HARDDEBUG 1
63 #endif
64
65 /*
66 * Returns number of elements in an array.
67 */
68 #define ALENGTH(a) (sizeof(a)/sizeof(a[0]))
69
70 /* Actual stack usage, note that this may include words in the redzone. */
71 # define STACK_SZ_ON_HEAP(p) (STACK_START(p) - STACK_TOP(p))
72
73 # define OverRunCheck(P) \
74 if ((P)->stop < (P)->htop) { \
75 erts_fprintf(stderr, "hend=%p\n", (P)->hend); \
76 erts_fprintf(stderr, "stop=%p\n", (P)->stop); \
77 erts_fprintf(stderr, "htop=%p\n", (P)->htop); \
78 erts_fprintf(stderr, "heap=%p\n", (P)->heap); \
79 erts_exit(ERTS_ABORT_EXIT, "%s, line %d: %T: Overrun stack and heap\n", \
80 __FILE__,__LINE__,(P)->common.id); \
81 }
82
83 #ifdef DEBUG
84 #define ErtsGcQuickSanityCheck(P) \
85 do { \
86 ASSERT((P)->heap < (P)->hend); \
87 ASSERT((p)->abandoned_heap || (P)->heap_sz == (P)->hend - (P)->heap); \
88 ASSERT((P)->heap <= (P)->htop && (P)->htop <= (P)->hend); \
89 ASSERT((P)->heap <= (P)->stop && (P)->stop <= (P)->hend); \
90 ASSERT((p)->abandoned_heap || ((P)->heap <= (P)->high_water && (P)->high_water <= (P)->hend)); \
91 OverRunCheck((P)); \
92 } while (0)
93 #else
94 #define ErtsGcQuickSanityCheck(P) \
95 do { \
96 OverRunCheck((P)); \
97 } while (0)
98 #endif
99 /*
100 * This structure describes the rootset for the GC.
101 */
102 typedef struct roots {
103 Eterm* v; /* Pointers to vectors with terms to GC
104 * (e.g. the stack).
105 */
106 Uint sz; /* Size of each vector. */
107 } Roots;
108
109 typedef struct {
110 Roots def[32]; /* Default storage. */
111 Roots* roots; /* Pointer to root set array. */
112 Uint size; /* Storage size. */
113 int num_roots; /* Number of root arrays. */
114 } Rootset;
115
116 static Uint setup_rootset(Process*, Eterm*, int, Rootset*);
117 static void cleanup_rootset(Rootset *rootset);
118 static Eterm *full_sweep_heaps(Process *p,
119 ErlHeapFragment *live_hf_end,
120 int hibernate,
121 Eterm *n_heap, Eterm* n_htop,
122 char *oh, Uint oh_size,
123 Eterm *objv, int nobj);
124 static int garbage_collect(Process* p, ErlHeapFragment *live_hf_end,
125 int need, Eterm* objv, int nobj, int fcalls,
126 Uint max_young_gen_usage);
127 static int major_collection(Process* p, ErlHeapFragment *live_hf_end,
128 int need, Eterm* objv, int nobj,
129 Uint ygen_usage, Uint *recl);
130 static int minor_collection(Process* p, ErlHeapFragment *live_hf_end,
131 int need, Eterm* objv, int nobj,
132 Uint ygen_usage, Uint *recl);
133 static void do_minor(Process *p, ErlHeapFragment *live_hf_end,
134 char *mature, Uint mature_size,
135 Uint new_sz, Eterm* objv, int nobj);
136 static Eterm *sweep_new_heap(Eterm *n_hp, Eterm *n_htop,
137 char* old_heap, Uint old_heap_size);
138 static Eterm *sweep_heaps(Eterm *n_hp, Eterm *n_htop,
139 char* old_heap, Uint old_heap_size);
140 static Eterm* sweep_literal_area(Eterm* n_hp, Eterm* n_htop,
141 char* old_heap, Uint old_heap_size,
142 char* src, Uint src_size);
143 static Eterm* sweep_literals_to_old_heap(Eterm* heap_ptr, Eterm* heap_end, Eterm* htop,
144 char* src, Uint src_size);
145 static Eterm* collect_live_heap_frags(Process* p, ErlHeapFragment *live_hf_end,
146 Eterm* htop);
147 static int adjust_after_fullsweep(Process *p, int need, Eterm *objv, int nobj);
148 static void shrink_new_heap(Process *p, Uint new_sz, Eterm *objv, int nobj);
149 static void grow_new_heap(Process *p, Uint new_sz, Eterm* objv, int nobj);
150 static void sweep_off_heap(Process *p, int fullsweep);
151 static void offset_heap(Eterm* hp, Uint sz, Sint offs, char* area, Uint area_size);
152 static void offset_heap_ptr(Eterm* hp, Uint sz, Sint offs, char* area, Uint area_size);
153 static void offset_rootset(Process *p, Sint offs, char* area, Uint area_size,
154 Eterm* objv, int nobj);
155 static void offset_off_heap(Process* p, Sint offs, char* area, Uint area_size);
156 static void offset_mqueue(Process *p, Sint offs, char* area, Uint area_size);
157 static int reached_max_heap_size(Process *p, Uint total_heap_size,
158 Uint extra_heap_size, Uint extra_old_heap_size);
159 static void init_gc_info(ErtsGCInfo *gcip);
160 static Uint64 next_vheap_size(Process* p, Uint64 vheap, Uint64 vheap_sz);
161
162 #ifdef HARDDEBUG
163 static void disallow_heap_frag_ref_in_heap(Process *p, Eterm *heap, Eterm *htop);
164 static void disallow_heap_frag_ref_in_old_heap(Process* p);
165 #endif
166
167 #if defined(ARCH_64)
168 # define MAX_HEAP_SIZES 154
169 #else
170 # define MAX_HEAP_SIZES 59
171 #endif
172
173 static Sint heap_sizes[MAX_HEAP_SIZES]; /* Suitable heap sizes. */
174 static int num_heap_sizes; /* Number of heap sizes. */
175
176 Uint erts_test_long_gc_sleep; /* Only used for testing... */
177
178 typedef struct {
179 Process *proc;
180 Eterm ref;
181 Eterm ref_heap[ERTS_REF_THING_SIZE];
182 Uint req_sched;
183 erts_atomic32_t refc;
184 } ErtsGCInfoReq;
185
186 static struct {
187 erts_mtx_t mtx;
188 ErtsGCInfo info;
189 } dirty_gc;
190
move_msgs_to_heap(Process * c_p)191 static void move_msgs_to_heap(Process *c_p)
192 {
193 Uint64 pre_oh, post_oh;
194
195 pre_oh = c_p->off_heap.overhead;
196 erts_proc_sig_move_msgs_to_heap(c_p);
197 post_oh = c_p->off_heap.overhead;
198
199 if (pre_oh != post_oh) {
200 /* Got new binaries; update bin vheap size... */
201 c_p->bin_vheap_sz = next_vheap_size(c_p, post_oh,
202 c_p->bin_vheap_sz);
203 }
204 }
205
206 static ERTS_INLINE int
gc_cost(Uint gc_moved_live_words,Uint resize_moved_words)207 gc_cost(Uint gc_moved_live_words, Uint resize_moved_words)
208 {
209 Sint reds;
210
211 reds = gc_moved_live_words/10;
212 reds += resize_moved_words/100;
213 if (reds < 1)
214 return 1;
215 if (reds > INT_MAX)
216 return INT_MAX;
217 return (int) reds;
218 }
219
220 ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(gcireq,
221 ErtsGCInfoReq,
222 5,
223 ERTS_ALC_T_GC_INFO_REQ)
224
225 /*
226 * Initialize GC global data.
227 */
228 void
erts_init_gc(void)229 erts_init_gc(void)
230 {
231 int i = 0, ix;
232 Sint max_heap_size = 0;
233
234 ERTS_CT_ASSERT(offsetof(ProcBin,thing_word) == offsetof(struct erl_off_heap_header,thing_word));
235 ERTS_CT_ASSERT(offsetof(ProcBin,thing_word) == offsetof(ErlFunThing,thing_word));
236 ERTS_CT_ASSERT(offsetof(ProcBin,thing_word) == offsetof(ExternalThing,header));
237 ERTS_CT_ASSERT(offsetof(ProcBin,size) == offsetof(struct erl_off_heap_header,size));
238 ERTS_CT_ASSERT(offsetof(ProcBin,size) == offsetof(ErlSubBin,size));
239 ERTS_CT_ASSERT(offsetof(ProcBin,size) == offsetof(ErlHeapBin,size));
240 ERTS_CT_ASSERT(offsetof(ProcBin,next) == offsetof(struct erl_off_heap_header,next));
241 ERTS_CT_ASSERT(offsetof(ProcBin,next) == offsetof(ErlFunThing,next));
242 ERTS_CT_ASSERT(offsetof(ProcBin,next) == offsetof(ExternalThing,next));
243
244 erts_test_long_gc_sleep = 0;
245
246 /*
247 * Heap sizes start growing in a Fibonacci sequence.
248 *
249 * Fib growth is not really ok for really large heaps, for
250 * example is fib(35) == 14meg, whereas fib(36) == 24meg;
251 * we really don't want that growth when the heaps are that big.
252 */
253
254 /* Growth stage 1 - Fibonacci + 1*/
255 /* 12,38 will hit size 233, the old default */
256
257 heap_sizes[0] = 12;
258 heap_sizes[1] = 38;
259
260 for(i = 2; i < 23; i++) {
261 /* one extra word for block header */
262 heap_sizes[i] = heap_sizes[i-1] + heap_sizes[i-2] + 1;
263 }
264
265
266 /* for 32 bit we want max_heap_size to be MAX(32bit) / 4 [words]
267 * for 64 bit we want max_heap_size to be MAX(52bit) / 8 [words]
268 */
269
270 max_heap_size = sizeof(Eterm) < 8 ? (Sint)((~(Uint)0)/(sizeof(Eterm))) :
271 (Sint)(((Uint64)1 << 53)/sizeof(Eterm));
272
273 /* Growth stage 2 - 20% growth */
274 /* At 1.3 mega words heap, we start to slow down. */
275 for (i = 23; i < ALENGTH(heap_sizes); i++) {
276 heap_sizes[i] = heap_sizes[i-1] + heap_sizes[i-1]/5;
277 if ((heap_sizes[i] < 0) || heap_sizes[i] > max_heap_size) {
278 /* Size turned negative. Discard this last size. */
279 i--;
280 break;
281 }
282 }
283 num_heap_sizes = i;
284
285 for (ix = 0; ix < erts_no_schedulers; ix++) {
286 ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(ix);
287 init_gc_info(&esdp->gc_info);
288 }
289
290 erts_mtx_init(&dirty_gc.mtx, "dirty_gc_info", NIL,
291 ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
292 init_gc_info(&dirty_gc.info);
293
294 init_gcireq_alloc();
295 }
296
297 /*
298 * Find the next heap size equal to or greater than the given size (if offset == 0).
299 *
300 * If offset is 1, the next higher heap size is returned (always greater than size).
301 */
302 Uint
erts_next_heap_size(Uint size,Uint offset)303 erts_next_heap_size(Uint size, Uint offset)
304 {
305 if (size < heap_sizes[0]) {
306 return heap_sizes[0];
307 } else {
308 Sint* low = heap_sizes;
309 Sint* high = heap_sizes + num_heap_sizes;
310 Sint* mid;
311
312 while (low < high) {
313 mid = low + (high-low) / 2;
314 if (size < mid[0]) {
315 high = mid;
316 } else if (size == mid[0]) {
317 ASSERT(mid+offset-heap_sizes < num_heap_sizes);
318 return mid[offset];
319 } else if (size < mid[1]) {
320 ASSERT(mid[0] < size && size <= mid[1]);
321 ASSERT(mid+offset-heap_sizes < num_heap_sizes);
322 return mid[offset+1];
323 } else {
324 low = mid + 1;
325 }
326 }
327 erts_exit(ERTS_ERROR_EXIT, "no next heap size found: %beu, offset %beu\n", size, offset);
328 }
329 return 0;
330 }
331 /*
332 * Return the next heap size to use. Make sure we never return
333 * a smaller heap size than the minimum heap size for the process.
334 * (Use of the erlang:hibernate/3 BIF could have shrinked the
335 * heap below the minimum heap size.)
336 */
337 static Uint
next_heap_size(Process * p,Uint size,Uint offset)338 next_heap_size(Process* p, Uint size, Uint offset)
339 {
340 size = erts_next_heap_size(size, offset);
341 return size < p->min_heap_size ? p->min_heap_size : size;
342 }
343
344 Eterm
erts_heap_sizes(Process * p)345 erts_heap_sizes(Process* p)
346 {
347 int i;
348 int n = 0;
349 int big = 0;
350 Eterm res = NIL;
351 Eterm* hp;
352 Eterm* bigp;
353
354 for (i = num_heap_sizes-1; i >= 0; i--) {
355 n += 2;
356 if (!IS_SSMALL(heap_sizes[i])) {
357 big += BIG_UINT_HEAP_SIZE;
358 }
359 }
360
361 /*
362 * We store all big numbers first on the heap, followed
363 * by all the cons cells.
364 */
365 bigp = HAlloc(p, n+big);
366 hp = bigp+big;
367 for (i = num_heap_sizes-1; i >= 0; i--) {
368 Eterm num;
369 Sint sz = heap_sizes[i];
370
371 if (IS_SSMALL(sz)) {
372 num = make_small(sz);
373 } else {
374 num = uint_to_big(sz, bigp);
375 bigp += BIG_UINT_HEAP_SIZE;
376 }
377 res = CONS(hp, num, res);
378 hp += 2;
379 }
380 return res;
381 }
382
383 void
erts_offset_heap(Eterm * hp,Uint sz,Sint offs,Eterm * low,Eterm * high)384 erts_offset_heap(Eterm* hp, Uint sz, Sint offs, Eterm* low, Eterm* high)
385 {
386 offset_heap(hp, sz, offs, (char*) low, ((char *)high)-((char *)low));
387 }
388
389 void
erts_offset_heap_ptr(Eterm * hp,Uint sz,Sint offs,Eterm * low,Eterm * high)390 erts_offset_heap_ptr(Eterm* hp, Uint sz, Sint offs,
391 Eterm* low, Eterm* high)
392 {
393 offset_heap_ptr(hp, sz, offs, (char *) low, ((char *)high)-((char *)low));
394 }
395
396
397 #define ptr_within(ptr, low, high) ((ptr) < (high) && (ptr) >= (low))
398
399 void
erts_offset_off_heap(ErlOffHeap * ohp,Sint offs,Eterm * low,Eterm * high)400 erts_offset_off_heap(ErlOffHeap *ohp, Sint offs, Eterm* low, Eterm* high)
401 {
402 if (ohp->first && ptr_within((Eterm *)ohp->first, low, high)) {
403 Eterm** uptr = (Eterm**) (void *) &ohp->first;
404 *uptr += offs;
405 }
406 }
407 #undef ptr_within
408
409 Eterm
erts_gc_after_bif_call_lhf(Process * p,ErlHeapFragment * live_hf_end,Eterm result,Eterm * regs,Uint arity)410 erts_gc_after_bif_call_lhf(Process* p, ErlHeapFragment *live_hf_end,
411 Eterm result, Eterm* regs, Uint arity)
412 {
413 int cost;
414
415 if (p->flags & F_HIBERNATE_SCHED) {
416 /*
417 * We just hibernated. We do *not* want to mess
418 * up the hibernation by an ordinary GC...
419 */
420 return result;
421 }
422
423 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ) {
424 erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
425 erts_proc_sig_fetch(p);
426 erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
427 }
428
429 if (!p->mbuf) {
430 /* Must have GC:d in BIF call... invalidate live_hf_end */
431 live_hf_end = ERTS_INVALID_HFRAG_PTR;
432 }
433
434 if (is_non_value(result)) {
435 if (p->freason == TRAP) {
436 cost = garbage_collect(p, live_hf_end, 0, regs, p->arity, p->fcalls, 0);
437 } else {
438 cost = garbage_collect(p, live_hf_end, 0, regs, arity, p->fcalls, 0);
439 }
440 } else {
441 Eterm val[1];
442
443 val[0] = result;
444 cost = garbage_collect(p, live_hf_end, 0, val, 1, p->fcalls, 0);
445 result = val[0];
446 }
447 BUMP_REDS(p, cost);
448
449 return result;
450 }
451
452 Eterm
erts_gc_after_bif_call(Process * p,Eterm result,Eterm * regs,Uint arity)453 erts_gc_after_bif_call(Process* p, Eterm result, Eterm* regs, Uint arity)
454 {
455 return erts_gc_after_bif_call_lhf(p, ERTS_INVALID_HFRAG_PTR,
456 result, regs, arity);
457 }
458
assert_no_active_writers(Process * p)459 static ERTS_INLINE void assert_no_active_writers(Process *p)
460 {
461 #ifdef DEBUG
462 struct erl_off_heap_header* ptr;
463 ptr = MSO(p).first;
464 while (ptr) {
465 if (ptr->thing_word == HEADER_PROC_BIN) {
466 ProcBin *pbp = (ProcBin*) ptr;
467 ERTS_ASSERT(!(pbp->flags & PB_ACTIVE_WRITER));
468 }
469 ptr = ptr->next;
470 }
471 #endif
472 }
473
474 #define ERTS_DELAY_GC_EXTRA_FREE 40
475 #define ERTS_ABANDON_HEAP_COST 10
476
477 static int
delay_garbage_collection(Process * p,ErlHeapFragment * live_hf_end,int need,int fcalls)478 delay_garbage_collection(Process *p, ErlHeapFragment *live_hf_end, int need, int fcalls)
479 {
480 ErlHeapFragment *hfrag;
481 Eterm *orig_heap, *orig_hend, *orig_htop, *orig_stop;
482 Eterm *stop, *hend;
483 Uint hsz, ssz;
484 int reds_left;
485
486 ERTS_HOLE_CHECK(p);
487
488 if ((p->flags & F_DISABLE_GC)
489 && p->live_hf_end == ERTS_INVALID_HFRAG_PTR) {
490 /*
491 * A BIF yielded with disabled GC. Remember
492 * heap fragments created by the BIF until we
493 * do next GC.
494 */
495 p->live_hf_end = live_hf_end;
496 }
497
498 if (need == 0) {
499 if (p->flags & (F_DIRTY_MAJOR_GC|F_DIRTY_MINOR_GC)) {
500 ASSERT(!ERTS_SCHEDULER_IS_DIRTY(erts_proc_sched_data(p)));
501 goto force_reschedule;
502 }
503 return 1;
504 }
505 /*
506 * Satisfy need in a heap fragment...
507 */
508 ASSERT(need > 0);
509
510 orig_heap = p->heap;
511 orig_hend = p->hend;
512 orig_htop = p->htop;
513 orig_stop = p->stop;
514
515 ssz = orig_hend - orig_stop;
516 hsz = ssz + need + ERTS_DELAY_GC_EXTRA_FREE + S_RESERVED;
517
518 hfrag = new_message_buffer(hsz);
519 p->heap = p->htop = &hfrag->mem[0];
520 p->hend = hend = &hfrag->mem[hsz];
521 p->stop = stop = hend - ssz;
522 sys_memcpy((void *) stop, (void *) orig_stop, ssz * sizeof(Eterm));
523
524 if (p->abandoned_heap) {
525 /*
526 * Active heap already in a fragment; adjust it and
527 * save it into mbuf list...
528 */
529 ErlHeapFragment *hfrag = ((ErlHeapFragment *)
530 (((char *) orig_heap)
531 - offsetof(ErlHeapFragment, mem)));
532 Uint used = orig_htop - orig_heap;
533 hfrag->used_size = used;
534 p->mbuf_sz += used;
535 ASSERT(hfrag->used_size <= hfrag->alloc_size);
536 ASSERT(!hfrag->off_heap.first && !hfrag->off_heap.overhead);
537 hfrag->next = p->mbuf;
538 p->mbuf = hfrag;
539 }
540 else {
541 /* Do not leave a hole in the abandoned heap... */
542 if (orig_htop < orig_hend) {
543 *orig_htop = make_pos_bignum_header(orig_hend-orig_htop-1);
544 if (orig_htop + 1 < orig_hend) {
545 orig_hend[-1] = (Uint) (orig_htop - orig_heap);
546 p->flags |= F_ABANDONED_HEAP_USE;
547 }
548 }
549 p->abandoned_heap = orig_heap;
550 }
551
552 #ifdef CHECK_FOR_HOLES
553 p->last_htop = p->htop;
554 p->heap_hfrag = hfrag;
555 #endif
556
557 force_reschedule:
558
559 /* Make sure that we do a proper GC as soon as possible... */
560 p->flags |= F_FORCE_GC;
561 reds_left = ERTS_REDS_LEFT(p, fcalls);
562 ASSERT(CONTEXT_REDS - reds_left >= erts_proc_sched_data(p)->virtual_reds);
563
564 if (reds_left > ERTS_ABANDON_HEAP_COST) {
565 int vreds = reds_left - ERTS_ABANDON_HEAP_COST;
566 erts_proc_sched_data((p))->virtual_reds += vreds;
567 }
568
569 ERTS_CHK_MBUF_SZ(p);
570
571 ASSERT(CONTEXT_REDS >= erts_proc_sched_data(p)->virtual_reds);
572 return reds_left;
573 }
574
575 static ERTS_FORCE_INLINE Uint
young_gen_usage(Process * p,Uint * ext_msg_usage)576 young_gen_usage(Process *p, Uint *ext_msg_usage)
577 {
578 Uint hsz;
579 Eterm *aheap;
580
581 ERTS_CHK_MBUF_SZ(p);
582
583 hsz = p->mbuf_sz;
584
585 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ) {
586 ERTS_FOREACH_SIG_PRIVQS(
587 p, mp,
588 {
589 /*
590 * We leave not yet decoded distribution messages
591 * as they are in the queue since it is not
592 * possible to determine a maximum size until
593 * actual decoding. However, we use their estimated
594 * size when calculating need, and by this making
595 * it more likely that they will fit on the heap
596 * when actually decoded.
597 *
598 * We however ignore off heap messages...
599 */
600 if (ERTS_SIG_IS_MSG(mp)
601 && mp->data.attached
602 && mp->data.attached != ERTS_MSG_COMBINED_HFRAG) {
603 Uint sz = erts_msg_attached_data_size(mp);
604 if (ERTS_SIG_IS_EXTERNAL_MSG(mp))
605 *ext_msg_usage += sz;
606 hsz += sz;
607 }
608 });
609 }
610
611 hsz += p->htop - p->heap;
612 aheap = p->abandoned_heap;
613 if (aheap) {
614 /* used in orig heap */
615 if (p->flags & F_ABANDONED_HEAP_USE)
616 hsz += aheap[p->heap_sz-1];
617 else
618 hsz += p->heap_sz;
619 }
620 return hsz;
621 }
622
623 #define ERTS_GET_ORIG_HEAP(Proc, Heap, HTop) \
624 do { \
625 Eterm *aheap__ = (Proc)->abandoned_heap; \
626 if (!aheap__) { \
627 (Heap) = (Proc)->heap; \
628 (HTop) = (Proc)->htop; \
629 } \
630 else { \
631 (Heap) = aheap__; \
632 if ((Proc)->flags & F_ABANDONED_HEAP_USE) \
633 (HTop) = aheap__ + aheap__[(Proc)->heap_sz-1]; \
634 else \
635 (HTop) = aheap__ + (Proc)->heap_sz; \
636 } \
637 } while (0)
638
639
640 static ERTS_INLINE void
check_for_possibly_long_gc(Process * p,Uint ygen_usage)641 check_for_possibly_long_gc(Process *p, Uint ygen_usage)
642 {
643 int major;
644 Uint sz;
645
646 major = (p->flags & F_NEED_FULLSWEEP) || GEN_GCS(p) >= MAX_GEN_GCS(p);
647
648 sz = ygen_usage;
649 sz += p->hend - p->stop;
650 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ)
651 sz += erts_proc_sig_privqs_len(p);
652 if (major)
653 sz += p->old_htop - p->old_heap;
654
655 if (sz >= ERTS_POTENTIALLY_LONG_GC_HSIZE) {
656 ASSERT(!(p->flags & (F_DISABLE_GC|F_DELAY_GC)));
657 p->flags |= major ? F_DIRTY_MAJOR_GC : F_DIRTY_MINOR_GC;
658 erts_schedule_dirty_sys_execution(p);
659 }
660 }
661
662
663 /*
664 * Garbage collect a process.
665 *
666 * p: Pointer to the process structure.
667 * need: Number of Eterm words needed on the heap.
668 * objv: Array of terms to add to rootset; that is to preserve.
669 * nobj: Number of objects in objv.
670 */
671 static int
garbage_collect(Process * p,ErlHeapFragment * live_hf_end,int need,Eterm * objv,int nobj,int fcalls,Uint max_young_gen_usage)672 garbage_collect(Process* p, ErlHeapFragment *live_hf_end,
673 int need, Eterm* objv, int nobj, int fcalls,
674 Uint max_young_gen_usage)
675 {
676 Uint reclaimed_now = 0;
677 Uint ygen_usage;
678 Uint ext_msg_usage = 0;
679 Eterm gc_trace_end_tag;
680 int reds;
681 ErtsMonotonicTime start_time;
682 ErtsSchedulerData *esdp = erts_proc_sched_data(p);
683 erts_aint32_t state;
684 #ifdef USE_VM_PROBES
685 DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
686 #endif
687 ERTS_MSACC_PUSH_STATE();
688
689 ERTS_UNDEF(start_time, 0);
690 ERTS_CHK_MBUF_SZ(p);
691
692 ASSERT(CONTEXT_REDS - ERTS_REDS_LEFT(p, fcalls) >= esdp->virtual_reds);
693
694 state = erts_atomic32_read_nob(&p->state);
695
696 if ((p->flags & (F_DISABLE_GC|F_DELAY_GC)) || state & ERTS_PSFLG_EXITING) {
697 delay_gc_before_start:
698 return delay_garbage_collection(p, live_hf_end, need, fcalls);
699 }
700
701 ygen_usage = max_young_gen_usage ? max_young_gen_usage : young_gen_usage(p, &ext_msg_usage);
702
703 if (!ERTS_SCHEDULER_IS_DIRTY(esdp)) {
704 check_for_possibly_long_gc(p, ygen_usage);
705 if (p->flags & (F_DIRTY_MAJOR_GC|F_DIRTY_MINOR_GC))
706 goto delay_gc_before_start;
707 }
708
709 if (p->abandoned_heap)
710 live_hf_end = ERTS_INVALID_HFRAG_PTR;
711 else if (p->live_hf_end != ERTS_INVALID_HFRAG_PTR)
712 live_hf_end = p->live_hf_end;
713
714 ERTS_MSACC_SET_STATE_CACHED(ERTS_MSACC_STATE_GC);
715
716 erts_atomic32_read_bor_nob(&p->state, ERTS_PSFLG_GC);
717 if (erts_system_monitor_long_gc != 0)
718 start_time = erts_get_monotonic_time(esdp);
719
720 ERTS_CHK_OFFHEAP(p);
721
722 ErtsGcQuickSanityCheck(p);
723
724 #ifdef USE_VM_PROBES
725 *pidbuf = '\0';
726 if (DTRACE_ENABLED(gc_major_start)
727 || DTRACE_ENABLED(gc_major_end)
728 || DTRACE_ENABLED(gc_minor_start)
729 || DTRACE_ENABLED(gc_minor_end)) {
730 dtrace_proc_str(p, pidbuf);
731 }
732 #endif
733 /*
734 * Test which type of GC to do.
735 */
736
737 if (GEN_GCS(p) < MAX_GEN_GCS(p) && !(FLAGS(p) & F_NEED_FULLSWEEP)) {
738 if (IS_TRACED_FL(p, F_TRACE_GC)) {
739 trace_gc(p, am_gc_minor_start, need, THE_NON_VALUE);
740 }
741 DTRACE2(gc_minor_start, pidbuf, need);
742 reds = minor_collection(p, live_hf_end, need + ext_msg_usage, objv, nobj,
743 ygen_usage, &reclaimed_now);
744 DTRACE2(gc_minor_end, pidbuf, reclaimed_now);
745 if (reds == -1) {
746 if (IS_TRACED_FL(p, F_TRACE_GC)) {
747 trace_gc(p, am_gc_minor_end, reclaimed_now, THE_NON_VALUE);
748 }
749 if (!ERTS_SCHEDULER_IS_DIRTY(esdp)) {
750 p->flags |= F_NEED_FULLSWEEP;
751 check_for_possibly_long_gc(p, ygen_usage);
752 if (p->flags & F_DIRTY_MAJOR_GC)
753 goto delay_gc_after_start;
754 }
755 goto do_major_collection;
756 }
757 if (ERTS_SCHEDULER_IS_DIRTY(esdp))
758 p->flags &= ~F_DIRTY_MINOR_GC;
759 gc_trace_end_tag = am_gc_minor_end;
760 } else {
761 do_major_collection:
762 ERTS_MSACC_SET_STATE_CACHED_X(ERTS_MSACC_STATE_GC_FULL);
763 if (IS_TRACED_FL(p, F_TRACE_GC)) {
764 trace_gc(p, am_gc_major_start, need, THE_NON_VALUE);
765 }
766 DTRACE2(gc_major_start, pidbuf, need);
767 reds = major_collection(p, live_hf_end, need + ext_msg_usage, objv, nobj,
768 ygen_usage, &reclaimed_now);
769 if (ERTS_SCHEDULER_IS_DIRTY(esdp))
770 p->flags &= ~(F_DIRTY_MAJOR_GC|F_DIRTY_MINOR_GC);
771 DTRACE2(gc_major_end, pidbuf, reclaimed_now);
772 gc_trace_end_tag = am_gc_major_end;
773 ERTS_MSACC_SET_STATE_CACHED_X(ERTS_MSACC_STATE_GC);
774 }
775
776 assert_no_active_writers(p);
777
778 /*
779 * Finish.
780 */
781
782 ERTS_CHK_OFFHEAP(p);
783
784 ErtsGcQuickSanityCheck(p);
785
786 /* Max heap size has been reached and the process was configured
787 to be killed, so we kill it and set it in a delayed garbage
788 collecting state. There should be no gc_end trace or
789 long_gc/large_gc triggers when this happens as process was
790 killed before a GC could be done. */
791 if (reds == -2) {
792 int res;
793
794 erts_set_self_exiting(p, am_killed);
795
796 delay_gc_after_start:
797 /* erts_send_exit_signal looks for ERTS_PSFLG_GC, so
798 we have to remove it after the signal is sent */
799 erts_atomic32_read_band_nob(&p->state, ~ERTS_PSFLG_GC);
800
801 /* We have to make sure that we have space for need on the heap */
802 res = delay_garbage_collection(p, live_hf_end, need, fcalls);
803 ERTS_MSACC_POP_STATE();
804 return res;
805 }
806
807 erts_atomic32_read_band_nob(&p->state, ~ERTS_PSFLG_GC);
808
809 if (IS_TRACED_FL(p, F_TRACE_GC)) {
810 trace_gc(p, gc_trace_end_tag, reclaimed_now, THE_NON_VALUE);
811 }
812
813 if (erts_system_monitor_long_gc != 0) {
814 ErtsMonotonicTime end_time;
815 Uint gc_time;
816 if (erts_test_long_gc_sleep)
817 while (0 != erts_milli_sleep(erts_test_long_gc_sleep));
818 end_time = erts_get_monotonic_time(esdp);
819 gc_time = (Uint) ERTS_MONOTONIC_TO_MSEC(end_time - start_time);
820 if (gc_time && gc_time > erts_system_monitor_long_gc) {
821 monitor_long_gc(p, gc_time);
822 }
823 }
824 if (erts_system_monitor_large_heap != 0) {
825 Uint size = HEAP_SIZE(p);
826 size += OLD_HEAP(p) ? OLD_HEND(p) - OLD_HEAP(p) : 0;
827 if (size >= erts_system_monitor_large_heap)
828 monitor_large_heap(p);
829 }
830
831 if (ERTS_SCHEDULER_IS_DIRTY(esdp)) {
832 erts_mtx_lock(&dirty_gc.mtx);
833 dirty_gc.info.garbage_cols++;
834 dirty_gc.info.reclaimed += reclaimed_now;
835 erts_mtx_unlock(&dirty_gc.mtx);
836 }
837 else
838 {
839 esdp->gc_info.garbage_cols++;
840 esdp->gc_info.reclaimed += reclaimed_now;
841 }
842
843 FLAGS(p) &= ~(F_FORCE_GC|F_HIBERNATED);
844 p->live_hf_end = ERTS_INVALID_HFRAG_PTR;
845
846 ERTS_MSACC_POP_STATE();
847
848 #ifdef CHECK_FOR_HOLES
849 /*
850 * We intentionally do not rescan the areas copied by the GC.
851 * We trust the GC not to leave any holes.
852 */
853 p->last_htop = p->htop;
854 p->last_mbuf = 0;
855 #endif
856
857 #ifdef DEBUG
858 /*
859 * The scanning for pointers from the old_heap into the new_heap or
860 * heap fragments turned out to be costly, so we remember how far we
861 * have scanned this time and will start scanning there next time.
862 * (We will not detect wild writes into the old heap, or modifications
863 * of the old heap in-between garbage collections.)
864 */
865 p->last_old_htop = p->old_htop;
866 #endif
867
868 ASSERT(!p->mbuf);
869 ASSERT(!ERTS_IS_GC_DESIRED(p));
870 ASSERT(need <= HEAP_LIMIT(p) - HEAP_TOP(p));
871
872 return reds;
873 }
874
875 int
erts_garbage_collect_nobump(Process * p,int need,Eterm * objv,int nobj,int fcalls)876 erts_garbage_collect_nobump(Process* p, int need, Eterm* objv, int nobj, int fcalls)
877 {
878 int reds, reds_left;
879 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ) {
880 erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
881 erts_proc_sig_fetch(p);
882 erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
883 }
884 reds = garbage_collect(p, ERTS_INVALID_HFRAG_PTR, need, objv, nobj, fcalls, 0);
885 reds_left = ERTS_REDS_LEFT(p, fcalls);
886 if (reds > reds_left)
887 reds = reds_left;
888 ASSERT(CONTEXT_REDS - (reds_left - reds) >= erts_proc_sched_data(p)->virtual_reds);
889 return reds;
890 }
891
892 void
erts_garbage_collect(Process * p,int need,Eterm * objv,int nobj)893 erts_garbage_collect(Process* p, int need, Eterm* objv, int nobj)
894 {
895 int reds;
896 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ) {
897 erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
898 erts_proc_sig_fetch(p);
899 erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
900 }
901 reds = garbage_collect(p, ERTS_INVALID_HFRAG_PTR, need, objv, nobj, p->fcalls, 0);
902 BUMP_REDS(p, reds);
903 ASSERT(CONTEXT_REDS - ERTS_BIF_REDS_LEFT(p)
904 >= erts_proc_sched_data(p)->virtual_reds);
905 }
906
907
908 /*
909 * Place all living data on a the new heap; deallocate any old heap.
910 * Meant to be used by hibernate/3.
911 */
912 static int
garbage_collect_hibernate(Process * p,int check_long_gc)913 garbage_collect_hibernate(Process* p, int check_long_gc)
914 {
915 Uint heap_size;
916 Eterm* heap;
917 Eterm* htop;
918 Uint actual_size;
919 char* area;
920 Uint area_size;
921 Sint offs;
922 int reds;
923
924 if (p->flags & F_DISABLE_GC)
925 ERTS_INTERNAL_ERROR("GC disabled");
926
927 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ) {
928 erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
929 erts_proc_sig_fetch(p);
930 erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
931 }
932
933 if (ERTS_SCHEDULER_IS_DIRTY(erts_proc_sched_data(p))) {
934 p->flags &= ~(F_DIRTY_GC_HIBERNATE|F_DIRTY_MAJOR_GC|F_DIRTY_MINOR_GC);
935 } else if (check_long_gc) {
936 Uint flags = p->flags;
937
938 p->flags |= F_NEED_FULLSWEEP;
939
940 check_for_possibly_long_gc(p, ((p->htop - p->heap) +
941 p->mbuf_sz + S_RESERVED));
942
943 if (p->flags & (F_DIRTY_MAJOR_GC|F_DIRTY_MINOR_GC)) {
944 p->flags = flags|F_DIRTY_GC_HIBERNATE;
945 return 1;
946 }
947
948 p->flags = flags;
949 }
950
951 /*
952 * Preliminaries.
953 */
954 erts_atomic32_read_bor_nob(&p->state, ERTS_PSFLG_GC);
955 ErtsGcQuickSanityCheck(p);
956
957 /* Only allow one continuation pointer. */
958 ASSERT(p->stop == p->hend - CP_SIZE);
959 ASSERT(p->stop[0] == make_cp(beam_normal_exit));
960
961 /*
962 * Do it.
963 */
964 heap_size = p->heap_sz + (p->old_htop - p->old_heap) + p->mbuf_sz;
965
966 /* Reserve place for continuation pointer and redzone */
967 heap_size += S_RESERVED;
968
969 heap = (Eterm*) ERTS_HEAP_ALLOC(ERTS_ALC_T_TMP_HEAP,
970 sizeof(Eterm)*heap_size);
971 htop = heap;
972
973 htop = full_sweep_heaps(p,
974 ERTS_INVALID_HFRAG_PTR,
975 1,
976 heap,
977 htop,
978 (char *) p->old_heap,
979 (char *) p->old_htop - (char *) p->old_heap,
980 p->arg_reg,
981 p->arity);
982
983 #ifdef HARDDEBUG
984 disallow_heap_frag_ref_in_heap(p, heap, htop);
985 #endif
986
987 erts_deallocate_young_generation(p);
988
989 p->heap = heap;
990 p->high_water = htop;
991 p->htop = htop;
992 p->hend = p->heap + heap_size;
993 p->stop = p->hend - CP_SIZE;
994 p->heap_sz = heap_size;
995
996 heap_size = actual_size = p->htop - p->heap;
997
998 /* Reserve place for continuation pointer and redzone */
999 heap_size += S_RESERVED;
1000
1001 FLAGS(p) &= ~F_FORCE_GC;
1002 p->live_hf_end = ERTS_INVALID_HFRAG_PTR;
1003
1004 /*
1005 * Move the heap to its final destination.
1006 *
1007 * IMPORTANT: We have garbage collected to a temporary heap and
1008 * then copy the result to a newly allocated heap of exact size.
1009 * This is intentional and important! Garbage collecting as usual
1010 * and then shrinking the heap by reallocating it caused serious
1011 * fragmentation problems when large amounts of processes were
1012 * hibernated.
1013 */
1014
1015 ASSERT(actual_size < p->heap_sz);
1016
1017 heap = ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP, sizeof(Eterm)*heap_size);
1018 sys_memcpy((void *) heap, (void *) p->heap, actual_size*sizeof(Eterm));
1019 ERTS_HEAP_FREE(ERTS_ALC_T_TMP_HEAP, p->heap, p->heap_sz*sizeof(Eterm));
1020
1021 p->hend = heap + heap_size;
1022 p->stop = p->hend - CP_SIZE;
1023 p->stop[0] = make_cp(beam_normal_exit);
1024
1025 offs = heap - p->heap;
1026 area = (char *) p->heap;
1027 area_size = ((char *) p->htop) - area;
1028 offset_heap(heap, actual_size, offs, area, area_size);
1029 p->high_water = heap + (p->high_water - p->heap);
1030 offset_rootset(p, offs, area, area_size, p->arg_reg, p->arity);
1031 p->htop = heap + actual_size;
1032 p->heap = heap;
1033 p->heap_sz = heap_size;
1034
1035
1036 #ifdef CHECK_FOR_HOLES
1037 p->last_htop = p->htop;
1038 p->last_mbuf = 0;
1039 #endif
1040 #ifdef DEBUG
1041 p->last_old_htop = NULL;
1042 #endif
1043
1044 /*
1045 * Finishing.
1046 */
1047
1048 ErtsGcQuickSanityCheck(p);
1049
1050 p->flags |= F_HIBERNATED;
1051
1052 erts_atomic32_read_band_nob(&p->state, ~ERTS_PSFLG_GC);
1053
1054 reds = gc_cost(actual_size, actual_size);
1055 return reds;
1056 }
1057
1058 void
erts_garbage_collect_hibernate(Process * p)1059 erts_garbage_collect_hibernate(Process* p)
1060 {
1061 int reds = garbage_collect_hibernate(p, 1);
1062 BUMP_REDS(p, reds);
1063 }
1064
1065 #define fullsweep_nstack(p,n_htop) (n_htop)
1066 #define GENSWEEP_NSTACK(p,old_htop,n_htop) do{}while(0)
1067 #define offset_nstack(p,offs,area,area_size) do{}while(0)
1068 #define sweep_literals_nstack(p,old_htop,area,area_size) (old_htop)
1069
1070 int
erts_garbage_collect_literals(Process * p,Eterm * literals,Uint byte_lit_size,struct erl_off_heap_header * oh,int fcalls)1071 erts_garbage_collect_literals(Process* p, Eterm* literals,
1072 Uint byte_lit_size,
1073 struct erl_off_heap_header* oh,
1074 int fcalls)
1075 {
1076 Uint lit_size = byte_lit_size / sizeof(Eterm);
1077 Uint old_heap_size;
1078 Eterm* temp_lit;
1079 Sint offs;
1080 Rootset rootset; /* Rootset for GC (stack, dictionary, etc). */
1081 Roots* roots;
1082 char* area;
1083 Uint area_size;
1084 Eterm* old_htop;
1085 Uint n;
1086 Uint ygen_usage = 0;
1087 Uint ext_msg_usage = 0;
1088 struct erl_off_heap_header** prev = NULL;
1089 Sint64 reds;
1090 int hibernated = !!(p->flags & F_HIBERNATED);
1091
1092 if (p->flags & (F_DISABLE_GC|F_DELAY_GC))
1093 ERTS_INTERNAL_ERROR("GC disabled");
1094
1095 /*
1096 * First an ordinary major collection...
1097 */
1098
1099 p->flags |= F_NEED_FULLSWEEP;
1100
1101 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ) {
1102 erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
1103 erts_proc_sig_fetch(p);
1104 erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
1105 }
1106
1107 if (ERTS_SCHEDULER_IS_DIRTY(erts_proc_sched_data(p)))
1108 p->flags &= ~F_DIRTY_CLA;
1109 else {
1110 Uint size = byte_lit_size/sizeof(Uint);
1111 ygen_usage = young_gen_usage(p, &ext_msg_usage);
1112 if (hibernated)
1113 size = size*2 + 3*ygen_usage;
1114 else
1115 size = size + 2*ygen_usage;
1116 check_for_possibly_long_gc(p, size);
1117 if (p->flags & F_DIRTY_MAJOR_GC) {
1118 p->flags |= F_DIRTY_CLA;
1119 return 10;
1120 }
1121 }
1122
1123 reds = (Sint64) garbage_collect(p, ERTS_INVALID_HFRAG_PTR, ext_msg_usage,
1124 p->arg_reg, p->arity, fcalls,
1125 ygen_usage);
1126 if (ERTS_PROC_IS_EXITING(p)) {
1127 return 0;
1128 }
1129
1130 ASSERT(!(p->flags & (F_DIRTY_MAJOR_GC|F_DIRTY_MINOR_GC)));
1131
1132 if (MAX_HEAP_SIZE_GET(p)) {
1133 Uint new_heap_size;
1134 Uint old_heap_size;
1135 Uint total_heap_size;
1136
1137 new_heap_size = HEAP_END(p) - HEAP_START(p);
1138 old_heap_size = erts_next_heap_size(lit_size, 0);
1139 total_heap_size = new_heap_size + old_heap_size;
1140 if (MAX_HEAP_SIZE_GET(p) < total_heap_size &&
1141 reached_max_heap_size(p, total_heap_size,
1142 new_heap_size, old_heap_size)) {
1143 erts_set_self_exiting(p, am_killed);
1144 return 0;
1145 }
1146 }
1147
1148 /*
1149 * Set GC state.
1150 */
1151 erts_atomic32_read_bor_nob(&p->state, ERTS_PSFLG_GC);
1152
1153 /*
1154 * Just did a major collection (which has discarded the old heap),
1155 * so that we don't have to cope with pointer to literals on the
1156 * old heap. We will now allocate an old heap to contain the literals.
1157 */
1158
1159 ASSERT(p->old_heap == 0); /* Must NOT have an old heap yet. */
1160 old_heap_size = erts_next_heap_size(lit_size, 0);
1161 p->old_heap = p->old_htop = (Eterm*) ERTS_HEAP_ALLOC(ERTS_ALC_T_OLD_HEAP,
1162 sizeof(Eterm)*old_heap_size);
1163 p->old_hend = p->old_heap + old_heap_size;
1164
1165 /*
1166 * We soon want to garbage collect the literals. But since a GC is
1167 * destructive (MOVED markers are written), we must copy the literals
1168 * to a temporary area and change all references to literals.
1169 */
1170 temp_lit = (Eterm *) erts_alloc(ERTS_ALC_T_TMP, byte_lit_size);
1171 sys_memcpy(temp_lit, literals, byte_lit_size);
1172 offs = temp_lit - literals;
1173 offset_heap(temp_lit, lit_size, offs, (char *) literals, byte_lit_size);
1174 offset_heap(p->heap, p->htop - p->heap, offs, (char *) literals, byte_lit_size);
1175 offset_rootset(p, offs, (char *) literals, byte_lit_size, p->arg_reg, p->arity);
1176 if (oh) {
1177 oh = (struct erl_off_heap_header *) ((Eterm *)(void *) oh + offs);
1178 }
1179
1180 /*
1181 * Now the literals are placed in memory that is safe to write into,
1182 * so now we GC the literals into the old heap. First we go through the
1183 * rootset.
1184 */
1185
1186 area = (char *) temp_lit;
1187 area_size = byte_lit_size;
1188 n = setup_rootset(p, p->arg_reg, p->arity, &rootset);
1189 roots = rootset.roots;
1190 old_htop = sweep_literals_nstack(p, p->old_htop, area, area_size);
1191 while (n--) {
1192 Eterm* g_ptr = roots->v;
1193 Uint g_sz = roots->sz;
1194 Eterm* ptr;
1195 Eterm val;
1196
1197 roots++;
1198
1199 for ( ; g_sz--; g_ptr++) {
1200 Eterm gval = *g_ptr;
1201
1202 switch (primary_tag(gval)) {
1203 case TAG_PRIMARY_BOXED:
1204 ptr = boxed_val(gval);
1205 val = *ptr;
1206 if (IS_MOVED_BOXED(val)) {
1207 ASSERT(is_boxed(val));
1208 *g_ptr = val;
1209 } else if (ErtsInArea(ptr, area, area_size)) {
1210 move_boxed(ptr,val,&old_htop,g_ptr);
1211 }
1212 break;
1213 case TAG_PRIMARY_LIST:
1214 ptr = list_val(gval);
1215 val = *ptr;
1216 if (IS_MOVED_CONS(val)) { /* Moved */
1217 *g_ptr = ptr[1];
1218 } else if (ErtsInArea(ptr, area, area_size)) {
1219 move_cons(ptr,val,&old_htop,g_ptr);
1220 }
1221 break;
1222 default:
1223 break;
1224 }
1225 }
1226 }
1227 ASSERT(p->old_htop <= old_htop && old_htop <= p->old_hend);
1228 cleanup_rootset(&rootset);
1229
1230 /*
1231 * Now all references in the rootset to the literals have been updated.
1232 * Now we'll have to go through all heaps updating all other references.
1233 */
1234
1235 old_htop = sweep_literals_to_old_heap(p->heap, p->htop, old_htop, area, area_size);
1236 old_htop = sweep_literal_area(p->old_heap, old_htop,
1237 (char *) p->old_heap, sizeof(Eterm)*old_heap_size,
1238 area, area_size);
1239 ASSERT(p->old_htop <= old_htop && old_htop <= p->old_hend);
1240 p->old_htop = old_htop;
1241
1242 /*
1243 * Prepare to sweep off-heap objects. Since all MSOs on the new
1244 * heap must be come before MSOs on the old heap, find the end of
1245 * current MSO list and use that as a starting point.
1246 */
1247
1248 if (oh) {
1249 prev = &MSO(p).first;
1250 while (*prev) {
1251 prev = &(*prev)->next;
1252 }
1253 }
1254
1255 /*
1256 * Sweep through all off-heap objects in the temporary literal area.
1257 */
1258
1259 while (oh) {
1260 if (IS_MOVED_BOXED(oh->thing_word)) {
1261 struct erl_off_heap_header* ptr;
1262
1263 /*
1264 * This off-heap object has been copied to the heap.
1265 * We must increment its reference count and
1266 * link it into the MSO list for the process.
1267 */
1268
1269 ptr = (struct erl_off_heap_header*) boxed_val(oh->thing_word);
1270 switch (thing_subtag(ptr->thing_word)) {
1271 case REFC_BINARY_SUBTAG:
1272 {
1273 Binary* bptr = ((ProcBin*)ptr)->val;
1274 erts_refc_inc(&bptr->intern.refc, 2);
1275 break;
1276 }
1277 case FUN_SUBTAG:
1278 {
1279 ErlFunEntry* fe = ((ErlFunThing*)ptr)->fe;
1280 erts_refc_inc(&fe->refc, 2);
1281 break;
1282 }
1283 case REF_SUBTAG:
1284 {
1285 ErtsMagicBinary *bptr;
1286 ASSERT(is_magic_ref_thing(ptr));
1287 bptr = ((ErtsMRefThing *) ptr)->mb;
1288 erts_refc_inc(&bptr->intern.refc, 2);
1289 break;
1290 }
1291 default:
1292 {
1293 ExternalThing *etp;
1294 ASSERT(is_external_header(ptr->thing_word));
1295 etp = (ExternalThing *) ptr;
1296 erts_ref_node_entry(etp->node, 2,
1297 make_boxed(&oh->thing_word));
1298 break;
1299 }
1300 }
1301 *prev = ptr;
1302 prev = &ptr->next;
1303 }
1304 oh = oh->next;
1305 }
1306
1307 if (prev) {
1308 *prev = NULL;
1309 }
1310
1311 /*
1312 * We no longer need this temporary area.
1313 */
1314 erts_free(ERTS_ALC_T_TMP, (void *) temp_lit);
1315
1316 /*
1317 * Restore status.
1318 */
1319 erts_atomic32_read_band_nob(&p->state, ~ERTS_PSFLG_GC);
1320
1321 reds += (Sint64) gc_cost((p->htop - p->heap) + byte_lit_size/sizeof(Uint), 0);
1322
1323 if (hibernated) {
1324 /* Restore the process into hibernated state... */
1325 reds += garbage_collect_hibernate(p, 0);
1326 }
1327
1328 if (reds > INT_MAX)
1329 return INT_MAX;
1330 return (int) reds;
1331 }
1332
1333 static int
minor_collection(Process * p,ErlHeapFragment * live_hf_end,int need,Eterm * objv,int nobj,Uint ygen_usage,Uint * recl)1334 minor_collection(Process* p, ErlHeapFragment *live_hf_end,
1335 int need, Eterm* objv, int nobj,
1336 Uint ygen_usage, Uint *recl)
1337 {
1338 Eterm *mature = p->abandoned_heap ? p->abandoned_heap : p->heap;
1339 Uint mature_size = p->high_water - mature;
1340 Uint size_before = ygen_usage;
1341 #ifdef DEBUG
1342 Uint debug_tmp = 0;
1343 #endif
1344
1345 /*
1346 * Check if we have gone past the max heap size limit
1347 */
1348
1349 if (MAX_HEAP_SIZE_GET(p)) {
1350 Uint heap_size = size_before,
1351 /* Note that we also count the un-allocated area
1352 in between the stack and heap */
1353 stack_size = HEAP_END(p) - HEAP_TOP(p),
1354 extra_heap_size,
1355 extra_old_heap_size = 0;
1356
1357 /* Add potential old heap size */
1358 if (OLD_HEAP(p) == NULL && mature_size != 0) {
1359 extra_old_heap_size = erts_next_heap_size(size_before, 1);
1360 heap_size += extra_old_heap_size;
1361 } else if (OLD_HEAP(p))
1362 heap_size += OLD_HEND(p) - OLD_HEAP(p);
1363
1364 /* Add potential new young heap size */
1365 extra_heap_size = next_heap_size(p, stack_size + size_before, 0);
1366 heap_size += extra_heap_size;
1367
1368 if (heap_size > MAX_HEAP_SIZE_GET(p))
1369 if (reached_max_heap_size(p, heap_size, extra_heap_size, extra_old_heap_size))
1370 return -2;
1371 }
1372
1373 /*
1374 * Allocate an old heap if we don't have one and if we'll need one.
1375 */
1376
1377 if (OLD_HEAP(p) == NULL && mature_size != 0) {
1378 Eterm* n_old;
1379
1380 /* Note: We choose a larger heap size than strictly needed,
1381 * which seems to reduce the number of fullsweeps.
1382 * This improved Estone by more than 1200 estones on my computer
1383 * (Ultra Sparc 10).
1384 */
1385 Uint new_sz = erts_next_heap_size(size_before, 1);
1386
1387 /* Create new, empty old_heap */
1388 n_old = (Eterm *) ERTS_HEAP_ALLOC(ERTS_ALC_T_OLD_HEAP,
1389 sizeof(Eterm)*new_sz);
1390
1391 OLD_HEND(p) = n_old + new_sz;
1392 OLD_HEAP(p) = OLD_HTOP(p) = n_old;
1393 }
1394
1395 /*
1396 * Do a minor collection if there is an old heap and if it
1397 * is large enough.
1398 */
1399
1400 if (OLD_HEAP(p) &&
1401 ((mature_size <= OLD_HEND(p) - OLD_HTOP(p)) &&
1402 ((BIN_OLD_VHEAP_SZ(p) > BIN_OLD_VHEAP(p))) ) ) {
1403 Eterm *prev_old_htop;
1404 Uint stack_size, size_after, adjust_size, need_after, new_sz, new_mature;
1405
1406 stack_size = STACK_START(p) - STACK_TOP(p);
1407 new_sz = stack_size + size_before;
1408 new_sz = next_heap_size(p, new_sz, 0);
1409
1410 prev_old_htop = p->old_htop;
1411 do_minor(p, live_hf_end, (char *) mature, mature_size*sizeof(Eterm),
1412 new_sz, objv, nobj);
1413
1414 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ)
1415 move_msgs_to_heap(p);
1416
1417 new_mature = p->old_htop - prev_old_htop;
1418
1419 size_after = new_mature;
1420 size_after += HEAP_TOP(p) - HEAP_START(p) + p->mbuf_sz;
1421 *recl += (size_before - size_after);
1422
1423 ErtsGcQuickSanityCheck(p);
1424
1425 GEN_GCS(p)++;
1426 need_after = ((HEAP_TOP(p) - HEAP_START(p))
1427 + need
1428 + stack_size
1429 + S_RESERVED);
1430
1431 /*
1432 * Excessively large heaps should be shrunk, but
1433 * don't even bother on reasonable small heaps.
1434 *
1435 * The reason for this is that after tenuring, we often
1436 * use a really small portion of new heap, therefore, unless
1437 * the heap size is substantial, we don't want to shrink.
1438 */
1439
1440 adjust_size = 0;
1441
1442 if ((HEAP_SIZE(p) > 3000) && (4 * need_after < HEAP_SIZE(p)) &&
1443 ((HEAP_SIZE(p) > 8000) ||
1444 (HEAP_SIZE(p) > (OLD_HEND(p) - OLD_HEAP(p))))) {
1445 Uint wanted = 3 * need_after;
1446 Uint old_heap_sz = OLD_HEND(p) - OLD_HEAP(p);
1447
1448 /*
1449 * Additional test to make sure we don't make the heap too small
1450 * compared to the size of the older generation heap.
1451 */
1452 if (wanted*9 < old_heap_sz) {
1453 Uint new_wanted = old_heap_sz / 8;
1454 if (new_wanted > wanted) {
1455 wanted = new_wanted;
1456 }
1457 }
1458
1459 wanted = wanted < MIN_HEAP_SIZE(p) ? MIN_HEAP_SIZE(p)
1460 : next_heap_size(p, wanted, 0);
1461 if (wanted < HEAP_SIZE(p)) {
1462 shrink_new_heap(p, wanted, objv, nobj);
1463 adjust_size = p->htop - p->heap;
1464 }
1465
1466 }
1467 else if (need_after > HEAP_SIZE(p)) {
1468 grow_new_heap(p, next_heap_size(p, need_after, 0), objv, nobj);
1469 adjust_size = p->htop - p->heap;
1470 }
1471 /*else: The heap size turned out to be just right. We are done. */
1472
1473 ASSERT(HEAP_SIZE(p) == next_heap_size(p, HEAP_SIZE(p), 0));
1474
1475 /* The heap usage during GC should be larger than what we end up
1476 after a GC, even if we grow it. If this assertion is not true
1477 we have to check size in grow_new_heap and potentially kill the
1478 process from there */
1479 ASSERT(!MAX_HEAP_SIZE_GET(p) ||
1480 !(MAX_HEAP_SIZE_FLAGS_GET(p) & MAX_HEAP_SIZE_KILL) ||
1481 MAX_HEAP_SIZE_GET(p) > (young_gen_usage(p, &debug_tmp) +
1482 (OLD_HEND(p) - OLD_HEAP(p)) +
1483 (HEAP_END(p) - HEAP_TOP(p))));
1484
1485 return gc_cost(size_after, adjust_size);
1486 }
1487
1488 /*
1489 * Not enough room for a minor collection. Must force a major collection.
1490 */
1491 return -1;
1492 }
1493
1494 static void
do_minor(Process * p,ErlHeapFragment * live_hf_end,char * mature,Uint mature_size,Uint new_sz,Eterm * objv,int nobj)1495 do_minor(Process *p, ErlHeapFragment *live_hf_end,
1496 char *mature, Uint mature_size,
1497 Uint new_sz, Eterm* objv, int nobj)
1498 {
1499 Rootset rootset; /* Rootset for GC (stack, dictionary, etc). */
1500 Roots* roots;
1501 Eterm* n_htop;
1502 Uint n;
1503 Eterm* ptr;
1504 Eterm val;
1505 Eterm gval;
1506 Eterm* old_htop = OLD_HTOP(p);
1507 Eterm* n_heap;
1508 char* oh = (char *) OLD_HEAP(p);
1509 Uint oh_size = (char *) OLD_HTOP(p) - oh;
1510
1511 VERBOSE(DEBUG_SHCOPY, ("[pid=%T] MINOR GC: %p %p %p %p\n", p->common.id,
1512 HEAP_START(p), HEAP_END(p), OLD_HEAP(p), OLD_HEND(p)));
1513
1514 n_htop = n_heap = (Eterm*) ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP,
1515 sizeof(Eterm)*new_sz);
1516
1517 n = setup_rootset(p, objv, nobj, &rootset);
1518 roots = rootset.roots;
1519
1520 /*
1521 * All allocations done. Start defile heap with move markers.
1522 * A crash dump due to allocation failure above will see a healthy heap.
1523 */
1524
1525 if (live_hf_end != ERTS_INVALID_HFRAG_PTR) {
1526 /*
1527 * Move heap frags that we know are completely live
1528 * directly into the new young heap generation.
1529 */
1530 n_htop = collect_live_heap_frags(p, live_hf_end, n_htop);
1531 }
1532
1533 GENSWEEP_NSTACK(p, old_htop, n_htop);
1534 while (n--) {
1535 Eterm* g_ptr = roots->v;
1536 Uint g_sz = roots->sz;
1537
1538 roots++;
1539 for ( ; g_sz--; g_ptr++) {
1540 gval = *g_ptr;
1541
1542 switch (primary_tag(gval)) {
1543
1544 case TAG_PRIMARY_BOXED: {
1545 ptr = boxed_val(gval);
1546 val = *ptr;
1547 if (IS_MOVED_BOXED(val)) {
1548 ASSERT(is_boxed(val));
1549 *g_ptr = val;
1550 } else if (ErtsInArea(ptr, mature, mature_size)) {
1551 move_boxed(ptr,val,&old_htop,g_ptr);
1552 } else if (ErtsInYoungGen(gval, ptr, oh, oh_size)) {
1553 move_boxed(ptr,val,&n_htop,g_ptr);
1554 }
1555 break;
1556 }
1557
1558 case TAG_PRIMARY_LIST: {
1559 ptr = list_val(gval);
1560 val = *ptr;
1561 if (IS_MOVED_CONS(val)) { /* Moved */
1562 *g_ptr = ptr[1];
1563 } else if (ErtsInArea(ptr, mature, mature_size)) {
1564 move_cons(ptr,val,&old_htop,g_ptr);
1565 } else if (ErtsInYoungGen(gval, ptr, oh, oh_size)) {
1566 move_cons(ptr,val,&n_htop,g_ptr);
1567 }
1568 break;
1569 }
1570 default:
1571 break;
1572 }
1573 }
1574 }
1575
1576 cleanup_rootset(&rootset);
1577
1578 /*
1579 * Now all references in the rootset point to the new heap. However,
1580 * most references on the new heap point to the old heap so the next stage
1581 * is to scan through the new heap evacuating data from the old heap
1582 * until all is changed.
1583 */
1584
1585 if (mature_size == 0) {
1586 n_htop = sweep_new_heap(n_heap, n_htop, oh, oh_size);
1587 } else {
1588 Eterm* n_hp = n_heap;
1589 Eterm* ptr;
1590 Eterm val;
1591 Eterm gval;
1592
1593 while (n_hp != n_htop) {
1594 ASSERT(n_hp < n_htop);
1595 gval = *n_hp;
1596 switch (primary_tag(gval)) {
1597 case TAG_PRIMARY_BOXED: {
1598 ptr = boxed_val(gval);
1599 val = *ptr;
1600 if (IS_MOVED_BOXED(val)) {
1601 ASSERT(is_boxed(val));
1602 *n_hp++ = val;
1603 } else if (ErtsInArea(ptr, mature, mature_size)) {
1604 move_boxed(ptr,val,&old_htop,n_hp++);
1605 } else if (ErtsInYoungGen(gval, ptr, oh, oh_size)) {
1606 move_boxed(ptr,val,&n_htop,n_hp++);
1607 } else {
1608 n_hp++;
1609 }
1610 break;
1611 }
1612 case TAG_PRIMARY_LIST: {
1613 ptr = list_val(gval);
1614 val = *ptr;
1615 if (IS_MOVED_CONS(val)) {
1616 *n_hp++ = ptr[1];
1617 } else if (ErtsInArea(ptr, mature, mature_size)) {
1618 move_cons(ptr,val,&old_htop,n_hp++);
1619 } else if (ErtsInYoungGen(gval, ptr, oh, oh_size)) {
1620 move_cons(ptr,val,&n_htop,n_hp++);
1621 } else {
1622 n_hp++;
1623 }
1624 break;
1625 }
1626 case TAG_PRIMARY_HEADER: {
1627 if (!header_is_thing(gval))
1628 n_hp++;
1629 else {
1630 if (header_is_bin_matchstate(gval)) {
1631 ErlBinMatchState *ms = (ErlBinMatchState*) n_hp;
1632 ErlBinMatchBuffer *mb = &(ms->mb);
1633 Eterm* origptr = &(mb->orig);
1634 ptr = boxed_val(*origptr);
1635 val = *ptr;
1636 if (IS_MOVED_BOXED(val)) {
1637 *origptr = val;
1638 mb->base = binary_bytes(val);
1639 } else if (ErtsInArea(ptr, mature, mature_size)) {
1640 move_boxed(ptr,val,&old_htop,origptr);
1641 mb->base = binary_bytes(mb->orig);
1642 } else if (ErtsInYoungGen(*origptr, ptr, oh, oh_size)) {
1643 move_boxed(ptr,val,&n_htop,origptr);
1644 mb->base = binary_bytes(mb->orig);
1645 }
1646 }
1647 n_hp += (thing_arityval(gval)+1);
1648 }
1649 break;
1650 }
1651 default:
1652 n_hp++;
1653 break;
1654 }
1655 }
1656 }
1657
1658 /*
1659 * And also if we have been tenuring, references on the second generation
1660 * may point to the old (soon to be deleted) new_heap.
1661 */
1662
1663 if (OLD_HTOP(p) < old_htop)
1664 old_htop = sweep_new_heap(OLD_HTOP(p), old_htop, oh, oh_size);
1665 OLD_HTOP(p) = old_htop;
1666 HIGH_WATER(p) = n_htop;
1667
1668 if (MSO(p).first) {
1669 sweep_off_heap(p, 0);
1670 }
1671
1672 #ifdef HARDDEBUG
1673 /*
1674 * Go through the old_heap before, and try to find references from the old_heap
1675 * into the old new_heap that has just been evacuated and is about to be freed
1676 * (as well as looking for reference into heap fragments, of course).
1677 */
1678 disallow_heap_frag_ref_in_old_heap(p);
1679 #endif
1680
1681 /* Copy stack to end of new heap */
1682 n = p->hend - p->stop;
1683 sys_memcpy(n_heap + new_sz - n, p->stop, n * sizeof(Eterm));
1684 p->stop = n_heap + new_sz - n;
1685
1686 #ifdef HARDDEBUG
1687 disallow_heap_frag_ref_in_heap(p, n_heap, n_htop);
1688 #endif
1689
1690 erts_deallocate_young_generation(p);
1691
1692 HEAP_START(p) = n_heap;
1693 HEAP_TOP(p) = n_htop;
1694 HEAP_END(p) = n_heap + new_sz;
1695
1696 #ifdef USE_VM_PROBES
1697 if (HEAP_SIZE(p) != new_sz && DTRACE_ENABLED(process_heap_grow)) {
1698 DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
1699 Uint old_sz = HEAP_SIZE(p);
1700
1701 HEAP_SIZE(p) = new_sz;
1702 dtrace_proc_str(p, pidbuf);
1703 DTRACE3(process_heap_grow, pidbuf, old_sz, new_sz);
1704 }
1705 #endif
1706 HEAP_SIZE(p) = new_sz;
1707 }
1708
1709 /*
1710 * Major collection. DISCARD the old heap.
1711 */
1712
1713 static int
major_collection(Process * p,ErlHeapFragment * live_hf_end,int need,Eterm * objv,int nobj,Uint ygen_usage,Uint * recl)1714 major_collection(Process* p, ErlHeapFragment *live_hf_end,
1715 int need, Eterm* objv, int nobj,
1716 Uint ygen_usage, Uint *recl)
1717 {
1718 Uint size_before, size_after, stack_size;
1719 Eterm* n_heap;
1720 Eterm* n_htop;
1721 char* oh = (char *) OLD_HEAP(p);
1722 Uint oh_size = (char *) OLD_HTOP(p) - oh;
1723 Uint new_sz, stk_sz;
1724 int adjusted;
1725
1726 VERBOSE(DEBUG_SHCOPY, ("[pid=%T] MAJOR GC: %p %p %p %p\n", p->common.id,
1727 HEAP_START(p), HEAP_END(p), OLD_HEAP(p), OLD_HEND(p)));
1728
1729 /*
1730 * Do a fullsweep GC. First figure out the size of the heap
1731 * to receive all live data.
1732 */
1733
1734 size_before = ygen_usage;
1735 size_before += p->old_htop - p->old_heap;
1736 stack_size = p->hend - p->stop;
1737
1738 new_sz = stack_size + size_before + S_RESERVED;
1739 new_sz = next_heap_size(p, new_sz, 0);
1740
1741 /*
1742 * Should we grow although we don't actually need to?
1743 */
1744
1745 if (new_sz == HEAP_SIZE(p) && FLAGS(p) & F_HEAP_GROW) {
1746 new_sz = next_heap_size(p, HEAP_SIZE(p), 1);
1747 }
1748
1749
1750 if (MAX_HEAP_SIZE_GET(p)) {
1751 Uint heap_size = size_before;
1752
1753 /* Add unused space in old heap */
1754 heap_size += OLD_HEND(p) - OLD_HTOP(p);
1755
1756 /* Add stack + unused space in young heap */
1757 heap_size += HEAP_END(p) - HEAP_TOP(p);
1758
1759 /* Add size of new young heap */
1760 heap_size += new_sz;
1761
1762 if (MAX_HEAP_SIZE_GET(p) < heap_size)
1763 if (reached_max_heap_size(p, heap_size, new_sz, 0))
1764 return -2;
1765 }
1766
1767 FLAGS(p) &= ~(F_HEAP_GROW|F_NEED_FULLSWEEP);
1768 n_htop = n_heap = (Eterm *) ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP,
1769 sizeof(Eterm)*new_sz);
1770
1771 n_htop = full_sweep_heaps(p, live_hf_end, 0, n_heap, n_htop, oh, oh_size,
1772 objv, nobj);
1773
1774 /* Move the stack to the end of the heap */
1775 stk_sz = HEAP_END(p) - p->stop;
1776 sys_memcpy(n_heap + new_sz - stk_sz, p->stop, stk_sz * sizeof(Eterm));
1777 p->stop = n_heap + new_sz - stk_sz;
1778
1779 #ifdef HARDDEBUG
1780 disallow_heap_frag_ref_in_heap(p, n_heap, n_htop);
1781 #endif
1782
1783 erts_deallocate_young_generation(p);
1784
1785 HEAP_START(p) = n_heap;
1786 HEAP_TOP(p) = n_htop;
1787 HEAP_END(p) = n_heap + new_sz;
1788
1789 #ifdef USE_VM_PROBES
1790 /* Fire process_heap_grow tracepoint after all heap references have
1791 * been updated. This allows to walk the stack. */
1792 if (HEAP_SIZE(p) != new_sz && DTRACE_ENABLED(process_heap_grow)) {
1793 DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
1794 Uint old_sz = HEAP_SIZE(p);
1795
1796 /* Update the heap size before firing tracepoint */
1797 HEAP_SIZE(p) = new_sz;
1798
1799 dtrace_proc_str(p, pidbuf);
1800 DTRACE3(process_heap_grow, pidbuf, old_sz, new_sz);
1801 }
1802 #endif
1803 HEAP_SIZE(p) = new_sz;
1804
1805 GEN_GCS(p) = 0;
1806
1807 HIGH_WATER(p) = HEAP_TOP(p);
1808
1809 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ)
1810 move_msgs_to_heap(p);
1811
1812 ErtsGcQuickSanityCheck(p);
1813
1814 size_after = HEAP_TOP(p) - HEAP_START(p) + p->mbuf_sz;
1815 *recl += size_before - size_after;
1816
1817 adjusted = adjust_after_fullsweep(p, need, objv, nobj);
1818
1819 ErtsGcQuickSanityCheck(p);
1820
1821 return gc_cost(size_after, adjusted ? size_after : 0);
1822 }
1823
1824 static Eterm *
full_sweep_heaps(Process * p,ErlHeapFragment * live_hf_end,int hibernate,Eterm * n_heap,Eterm * n_htop,char * oh,Uint oh_size,Eterm * objv,int nobj)1825 full_sweep_heaps(Process *p,
1826 ErlHeapFragment *live_hf_end,
1827 int hibernate,
1828 Eterm *n_heap, Eterm* n_htop,
1829 char *oh, Uint oh_size,
1830 Eterm *objv, int nobj)
1831 {
1832 Rootset rootset;
1833 Roots *roots;
1834 Uint n;
1835
1836 /*
1837 * Copy all top-level terms directly referenced by the rootset to
1838 * the new new_heap.
1839 */
1840
1841 n = setup_rootset(p, objv, nobj, &rootset);
1842
1843 /*
1844 * All allocations done. Start defile heap with move markers.
1845 * A crash dump due to allocation failure above will see a healthy heap.
1846 */
1847
1848 if (live_hf_end != ERTS_INVALID_HFRAG_PTR) {
1849 /*
1850 * Move heap frags that we know are completely live
1851 * directly into the heap.
1852 */
1853 n_htop = collect_live_heap_frags(p, live_hf_end, n_htop);
1854 }
1855
1856 roots = rootset.roots;
1857 while (n--) {
1858 Eterm* g_ptr = roots->v;
1859 Eterm g_sz = roots->sz;
1860
1861 roots++;
1862 for ( ; g_sz--; g_ptr++) {
1863 Eterm* ptr;
1864 Eterm val;
1865 Eterm gval = *g_ptr;
1866
1867 switch (primary_tag(gval)) {
1868
1869 case TAG_PRIMARY_BOXED: {
1870 ptr = boxed_val(gval);
1871 val = *ptr;
1872 if (IS_MOVED_BOXED(val)) {
1873 ASSERT(is_boxed(val));
1874 *g_ptr = val;
1875 } else if (!erts_is_literal(gval, ptr)) {
1876 move_boxed(ptr,val,&n_htop,g_ptr);
1877 }
1878 break;
1879 }
1880
1881 case TAG_PRIMARY_LIST: {
1882 ptr = list_val(gval);
1883 val = *ptr;
1884 if (IS_MOVED_CONS(val)) {
1885 *g_ptr = ptr[1];
1886 } else if (!erts_is_literal(gval, ptr)) {
1887 move_cons(ptr,val,&n_htop,g_ptr);
1888 }
1889 break;
1890 }
1891
1892 default:
1893 break;
1894 }
1895 }
1896 }
1897
1898 cleanup_rootset(&rootset);
1899
1900 /*
1901 * Now all references on the stack point to the new heap. However,
1902 * most references on the new heap point to the old heap so the next stage
1903 * is to scan through the new heap evacuating data from the old heap
1904 * until all is copied.
1905 */
1906
1907 n_htop = sweep_heaps(n_heap, n_htop, oh, oh_size);
1908
1909 if (MSO(p).first) {
1910 sweep_off_heap(p, 1);
1911 }
1912
1913 if (OLD_HEAP(p) != NULL) {
1914 ERTS_HEAP_FREE(ERTS_ALC_T_OLD_HEAP,
1915 OLD_HEAP(p),
1916 (OLD_HEND(p) - OLD_HEAP(p)) * sizeof(Eterm));
1917 OLD_HEAP(p) = OLD_HTOP(p) = OLD_HEND(p) = NULL;
1918 }
1919
1920 return n_htop;
1921 }
1922
1923 static int
adjust_after_fullsweep(Process * p,int need,Eterm * objv,int nobj)1924 adjust_after_fullsweep(Process *p, int need, Eterm *objv, int nobj)
1925 {
1926 int adjusted = 0;
1927 Uint wanted, sz, need_after;
1928 Uint stack_size = STACK_SZ_ON_HEAP(p);
1929
1930 /*
1931 * Resize the heap if needed.
1932 */
1933
1934 need_after = (HEAP_TOP(p) - HEAP_START(p)) + need + stack_size + S_RESERVED;
1935 if (HEAP_SIZE(p) < need_after) {
1936 /* Too small - grow to match requested need */
1937 sz = next_heap_size(p, need_after, 0);
1938 grow_new_heap(p, sz, objv, nobj);
1939 adjusted = 1;
1940 } else if (3 * HEAP_SIZE(p) < 4 * need_after){
1941 /* Need more than 75% of current, postpone to next GC.*/
1942 FLAGS(p) |= F_HEAP_GROW;
1943 } else if (4 * need_after < HEAP_SIZE(p) && HEAP_SIZE(p) > H_MIN_SIZE){
1944 /* We need less than 25% of the current heap, shrink.*/
1945 /* XXX - This is how it was done in the old GC:
1946 wanted = 4 * need_after;
1947 I think this is better as fullsweep is used mainly on
1948 small memory systems, but I could be wrong... */
1949 wanted = 2 * need_after;
1950
1951 sz = wanted < p->min_heap_size ? p->min_heap_size
1952 : next_heap_size(p, wanted, 0);
1953
1954 if (sz < HEAP_SIZE(p)) {
1955 shrink_new_heap(p, sz, objv, nobj);
1956 adjusted = 1;
1957 }
1958 }
1959 return adjusted;
1960 }
1961
1962 void
erts_deallocate_young_generation(Process * c_p)1963 erts_deallocate_young_generation(Process *c_p)
1964 {
1965 Eterm *orig_heap;
1966
1967 if (!c_p->abandoned_heap) {
1968 orig_heap = c_p->heap;
1969 ASSERT(!(c_p->flags & F_ABANDONED_HEAP_USE));
1970 }
1971 else {
1972 ErlHeapFragment *hfrag;
1973
1974 orig_heap = c_p->abandoned_heap;
1975 c_p->abandoned_heap = NULL;
1976 c_p->flags &= ~F_ABANDONED_HEAP_USE;
1977
1978 /*
1979 * Temporary heap located in heap fragment
1980 * only referred to by 'c_p->heap'. Add it to
1981 * 'c_p->mbuf' list and deallocate it as any
1982 * other heap fragment...
1983 */
1984 hfrag = ((ErlHeapFragment *)
1985 (((char *) c_p->heap)
1986 - offsetof(ErlHeapFragment, mem)));
1987
1988 ASSERT(!hfrag->off_heap.first);
1989 ASSERT(!hfrag->off_heap.overhead);
1990 ASSERT(!hfrag->next);
1991 ASSERT(c_p->htop - c_p->heap <= hfrag->alloc_size);
1992
1993 hfrag->next = c_p->mbuf;
1994 c_p->mbuf = hfrag;
1995 }
1996
1997 if (c_p->mbuf) {
1998 free_message_buffer(c_p->mbuf);
1999 c_p->mbuf = NULL;
2000 }
2001 if (c_p->msg_frag) {
2002 erts_cleanup_messages(c_p->msg_frag);
2003 c_p->msg_frag = NULL;
2004 }
2005 c_p->mbuf_sz = 0;
2006
2007 ERTS_HEAP_FREE(ERTS_ALC_T_HEAP,
2008 orig_heap,
2009 c_p->heap_sz * sizeof(Eterm));
2010 }
2011
2012 #ifdef HARDDEBUG
2013
2014 /*
2015 * Routines to verify that we don't have pointer into heap fragments from
2016 * that are not allowed to have them.
2017 *
2018 * For performance reasons, we use _unchecked_list_val(), _unchecked_boxed_val(),
2019 * and so on to avoid a function call.
2020 */
2021
2022 static void
disallow_heap_frag_ref_in_heap(Process * p,Eterm * heap,Eterm * htop)2023 disallow_heap_frag_ref_in_heap(Process *p, Eterm *heap, Eterm *htop)
2024 {
2025 Eterm* hp;
2026 Uint heap_size;
2027
2028 if (p->mbuf == 0) {
2029 return;
2030 }
2031
2032 heap_size = (htop - heap)*sizeof(Eterm);
2033
2034 hp = heap;
2035 while (hp < htop) {
2036 ErlHeapFragment* qb;
2037 Eterm* ptr;
2038 Eterm val;
2039
2040 val = *hp++;
2041 switch (primary_tag(val)) {
2042 case TAG_PRIMARY_BOXED:
2043 ptr = _unchecked_boxed_val(val);
2044 if (!ErtsInArea(ptr, heap, heap_size)) {
2045 for (qb = MBUF(p); qb != NULL; qb = qb->next) {
2046 if (ErtsInArea(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) {
2047 abort();
2048 }
2049 }
2050 }
2051 break;
2052 case TAG_PRIMARY_LIST:
2053 ptr = _unchecked_list_val(val);
2054 if (!ErtsInArea(ptr, heap, heap_size)) {
2055 for (qb = MBUF(p); qb != NULL; qb = qb->next) {
2056 if (ErtsInArea(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) {
2057 abort();
2058 }
2059 }
2060 }
2061 break;
2062 case TAG_PRIMARY_HEADER:
2063 if (header_is_thing(val)) {
2064 hp += _unchecked_thing_arityval(val);
2065 }
2066 break;
2067 }
2068 }
2069 }
2070
2071 static void
disallow_heap_frag_ref_in_old_heap(Process * p)2072 disallow_heap_frag_ref_in_old_heap(Process* p)
2073 {
2074 Eterm* hp;
2075 Eterm* htop;
2076 Eterm* old_heap;
2077 Uint old_heap_size;
2078 Eterm* new_heap;
2079 Uint new_heap_size;
2080
2081 htop = p->old_htop;
2082 old_heap = p->old_heap;
2083 old_heap_size = (htop - old_heap)*sizeof(Eterm);
2084 new_heap = p->heap;
2085 new_heap_size = (p->htop - new_heap)*sizeof(Eterm);
2086
2087 ASSERT(!p->last_old_htop
2088 || (old_heap <= p->last_old_htop && p->last_old_htop <= htop));
2089 hp = p->last_old_htop ? p->last_old_htop : old_heap;
2090 while (hp < htop) {
2091 ErlHeapFragment* qb;
2092 Eterm* ptr;
2093 Eterm val;
2094
2095 val = *hp++;
2096 switch (primary_tag(val)) {
2097 case TAG_PRIMARY_BOXED:
2098 ptr = (Eterm *) val;
2099 if (!ErtsInArea(ptr, old_heap, old_heap_size)) {
2100 if (ErtsInArea(ptr, new_heap, new_heap_size)) {
2101 abort();
2102 }
2103 for (qb = MBUF(p); qb != NULL; qb = qb->next) {
2104 if (ErtsInArea(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) {
2105 abort();
2106 }
2107 }
2108 }
2109 break;
2110 case TAG_PRIMARY_LIST:
2111 ptr = (Eterm *) val;
2112 if (!ErtsInArea(ptr, old_heap, old_heap_size)) {
2113 if (ErtsInArea(ptr, new_heap, new_heap_size)) {
2114 abort();
2115 }
2116 for (qb = MBUF(p); qb != NULL; qb = qb->next) {
2117 if (ErtsInArea(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) {
2118 abort();
2119 }
2120 }
2121 }
2122 break;
2123 case TAG_PRIMARY_HEADER:
2124 if (header_is_thing(val)) {
2125 hp += _unchecked_thing_arityval(val);
2126 if (!ErtsInArea(hp, old_heap, old_heap_size+1)) {
2127 abort();
2128 }
2129 }
2130 break;
2131 }
2132 }
2133 }
2134 #endif
2135
2136 typedef enum {
2137 ErtsSweepNewHeap,
2138 ErtsSweepHeaps,
2139 ErtsSweepLiteralArea
2140 } ErtsSweepType;
2141
2142 static ERTS_FORCE_INLINE Eterm *
sweep(Eterm * n_hp,Eterm * n_htop,ErtsSweepType type,char * oh,Uint ohsz,char * src,Uint src_size)2143 sweep(Eterm *n_hp, Eterm *n_htop,
2144 ErtsSweepType type,
2145 char *oh, Uint ohsz,
2146 char *src, Uint src_size)
2147 {
2148 Eterm* ptr;
2149 Eterm val;
2150 Eterm gval;
2151
2152 #undef ERTS_IS_IN_SWEEP_AREA
2153
2154 #define ERTS_IS_IN_SWEEP_AREA(TPtr, Ptr) \
2155 (type == ErtsSweepHeaps \
2156 ? !erts_is_literal((TPtr), (Ptr)) \
2157 : (type == ErtsSweepNewHeap \
2158 ? ErtsInYoungGen((TPtr), (Ptr), oh, ohsz) \
2159 : ErtsInArea((Ptr), src, src_size)))
2160
2161 while (n_hp != n_htop) {
2162 ASSERT(n_hp < n_htop);
2163 gval = *n_hp;
2164 switch (primary_tag(gval)) {
2165 case TAG_PRIMARY_BOXED: {
2166 ptr = boxed_val(gval);
2167 val = *ptr;
2168 if (IS_MOVED_BOXED(val)) {
2169 ASSERT(is_boxed(val));
2170 *n_hp++ = val;
2171 } else if (ERTS_IS_IN_SWEEP_AREA(gval, ptr)) {
2172 move_boxed(ptr,val,&n_htop,n_hp++);
2173 } else {
2174 n_hp++;
2175 }
2176 break;
2177 }
2178 case TAG_PRIMARY_LIST: {
2179 ptr = list_val(gval);
2180 val = *ptr;
2181 if (IS_MOVED_CONS(val)) {
2182 *n_hp++ = ptr[1];
2183 } else if (ERTS_IS_IN_SWEEP_AREA(gval, ptr)) {
2184 move_cons(ptr,val,&n_htop,n_hp++);
2185 } else {
2186 n_hp++;
2187 }
2188 break;
2189 }
2190 case TAG_PRIMARY_HEADER: {
2191 if (!header_is_thing(gval)) {
2192 n_hp++;
2193 } else {
2194 if (header_is_bin_matchstate(gval)) {
2195 ErlBinMatchState *ms = (ErlBinMatchState*) n_hp;
2196 ErlBinMatchBuffer *mb = &(ms->mb);
2197 Eterm* origptr;
2198 origptr = &(mb->orig);
2199 ptr = boxed_val(*origptr);
2200 val = *ptr;
2201 if (IS_MOVED_BOXED(val)) {
2202 *origptr = val;
2203 mb->base = binary_bytes(*origptr);
2204 } else if (ERTS_IS_IN_SWEEP_AREA(*origptr, ptr)) {
2205 move_boxed(ptr,val,&n_htop,origptr);
2206 mb->base = binary_bytes(*origptr);
2207 }
2208 }
2209 n_hp += (thing_arityval(gval)+1);
2210 }
2211 break;
2212 }
2213 default:
2214 n_hp++;
2215 break;
2216 }
2217 }
2218 return n_htop;
2219 #undef ERTS_IS_IN_SWEEP_AREA
2220 }
2221
2222 static Eterm *
sweep_new_heap(Eterm * n_hp,Eterm * n_htop,char * old_heap,Uint old_heap_size)2223 sweep_new_heap(Eterm *n_hp, Eterm *n_htop, char* old_heap, Uint old_heap_size)
2224 {
2225 return sweep(n_hp, n_htop,
2226 ErtsSweepNewHeap,
2227 old_heap, old_heap_size,
2228 NULL, 0);
2229 }
2230
2231 static Eterm *
sweep_heaps(Eterm * n_hp,Eterm * n_htop,char * old_heap,Uint old_heap_size)2232 sweep_heaps(Eterm *n_hp, Eterm *n_htop, char* old_heap, Uint old_heap_size)
2233 {
2234 return sweep(n_hp, n_htop,
2235 ErtsSweepHeaps,
2236 old_heap, old_heap_size,
2237 NULL, 0);
2238 }
2239
2240 static Eterm *
sweep_literal_area(Eterm * n_hp,Eterm * n_htop,char * old_heap,Uint old_heap_size,char * src,Uint src_size)2241 sweep_literal_area(Eterm *n_hp, Eterm *n_htop,
2242 char* old_heap, Uint old_heap_size,
2243 char* src, Uint src_size)
2244 {
2245 return sweep(n_hp, n_htop,
2246 ErtsSweepLiteralArea,
2247 old_heap, old_heap_size,
2248 src, src_size);
2249 }
2250
2251 static Eterm*
sweep_literals_to_old_heap(Eterm * heap_ptr,Eterm * heap_end,Eterm * htop,char * src,Uint src_size)2252 sweep_literals_to_old_heap(Eterm* heap_ptr, Eterm* heap_end, Eterm* htop,
2253 char* src, Uint src_size)
2254 {
2255 while (heap_ptr < heap_end) {
2256 Eterm* ptr;
2257 Eterm val;
2258 Eterm gval = *heap_ptr;
2259
2260 switch (primary_tag(gval)) {
2261 case TAG_PRIMARY_BOXED: {
2262 ptr = boxed_val(gval);
2263 val = *ptr;
2264 if (IS_MOVED_BOXED(val)) {
2265 ASSERT(is_boxed(val));
2266 *heap_ptr++ = val;
2267 } else if (ErtsInArea(ptr, src, src_size)) {
2268 move_boxed(ptr,val,&htop,heap_ptr++);
2269 } else {
2270 heap_ptr++;
2271 }
2272 break;
2273 }
2274 case TAG_PRIMARY_LIST: {
2275 ptr = list_val(gval);
2276 val = *ptr;
2277 if (IS_MOVED_CONS(val)) {
2278 *heap_ptr++ = ptr[1];
2279 } else if (ErtsInArea(ptr, src, src_size)) {
2280 move_cons(ptr,val,&htop,heap_ptr++);
2281 } else {
2282 heap_ptr++;
2283 }
2284 break;
2285 }
2286 case TAG_PRIMARY_HEADER: {
2287 if (!header_is_thing(gval)) {
2288 heap_ptr++;
2289 } else {
2290 if (header_is_bin_matchstate(gval)) {
2291 ErlBinMatchState *ms = (ErlBinMatchState*) heap_ptr;
2292 ErlBinMatchBuffer *mb = &(ms->mb);
2293 Eterm* origptr;
2294 origptr = &(mb->orig);
2295 ptr = boxed_val(*origptr);
2296 val = *ptr;
2297 if (IS_MOVED_BOXED(val)) {
2298 *origptr = val;
2299 mb->base = binary_bytes(*origptr);
2300 } else if (ErtsInArea(ptr, src, src_size)) {
2301 move_boxed(ptr,val,&htop,origptr);
2302 mb->base = binary_bytes(*origptr);
2303 }
2304 }
2305 heap_ptr += (thing_arityval(gval)+1);
2306 }
2307 break;
2308 }
2309 default:
2310 heap_ptr++;
2311 break;
2312 }
2313 }
2314 return htop;
2315 }
2316
2317 /*
2318 * Move an area (heap fragment) by sweeping over it and set move markers.
2319 */
2320 static Eterm*
move_one_area(Eterm * n_htop,char * src,Uint src_size)2321 move_one_area(Eterm* n_htop, char* src, Uint src_size)
2322 {
2323 Eterm* ptr = (Eterm*) src;
2324 Eterm* end = ptr + src_size/sizeof(Eterm);
2325 Eterm dummy_ref;
2326
2327 while (ptr != end) {
2328 Eterm val;
2329 ASSERT(ptr < end);
2330 val = *ptr;
2331 ASSERT(val != ERTS_HOLE_MARKER);
2332 if (is_header(val)) {
2333 ASSERT(ptr + header_arity(val) < end);
2334 ptr = move_boxed(ptr, val, &n_htop, &dummy_ref);
2335 }
2336 else { /* must be a cons cell */
2337 ASSERT(ptr+1 < end);
2338 move_cons(ptr, val, &n_htop, &dummy_ref);
2339 ptr += 2;
2340 }
2341 }
2342
2343 return n_htop;
2344 }
2345
2346 /*
2347 * Collect heap fragments and check that they point in the correct direction.
2348 */
2349
2350 static Eterm*
collect_live_heap_frags(Process * p,ErlHeapFragment * live_hf_end,Eterm * n_htop)2351 collect_live_heap_frags(Process* p, ErlHeapFragment *live_hf_end, Eterm* n_htop)
2352 {
2353 ErlHeapFragment* qb;
2354 char* frag_begin;
2355 Uint frag_size;
2356
2357 /*
2358 * Move the heap fragments to the new heap. Note that no GC is done on
2359 * the heap fragments. Any garbage will thus be moved as well and survive
2360 * until next GC.
2361 */
2362 qb = MBUF(p);
2363 while (qb != live_hf_end) {
2364 ASSERT(!qb->off_heap.first); /* process fragments use the MSO(p) list */
2365 frag_size = qb->used_size * sizeof(Eterm);
2366 if (frag_size != 0) {
2367 frag_begin = (char *) qb->mem;
2368 n_htop = move_one_area(n_htop, frag_begin, frag_size);
2369 }
2370 qb = qb->next;
2371 }
2372 return n_htop;
2373 }
2374
2375 void
erts_copy_one_frag(Eterm ** hpp,ErlOffHeap * off_heap,ErlHeapFragment * bp,Eterm * refs,int nrefs)2376 erts_copy_one_frag(Eterm** hpp, ErlOffHeap* off_heap,
2377 ErlHeapFragment *bp, Eterm *refs, int nrefs)
2378 {
2379 Uint sz;
2380 int i;
2381 Sint offs;
2382 struct erl_off_heap_header* oh;
2383 Eterm *fhp, *hp;
2384
2385 OH_OVERHEAD(off_heap, bp->off_heap.overhead);
2386 sz = bp->used_size;
2387
2388 fhp = bp->mem;
2389 hp = *hpp;
2390 offs = hp - fhp;
2391
2392 oh = NULL;
2393 while (sz--) {
2394 Uint cpy_sz;
2395 Eterm val = *fhp++;
2396
2397 switch (primary_tag(val)) {
2398 case TAG_PRIMARY_IMMED1:
2399 *hp++ = val;
2400 break;
2401 case TAG_PRIMARY_LIST:
2402 if (erts_is_literal(val,list_val(val))) {
2403 *hp++ = val;
2404 } else {
2405 *hp++ = offset_ptr(val, offs);
2406 }
2407 break;
2408 case TAG_PRIMARY_BOXED:
2409 if (erts_is_literal(val,boxed_val(val))) {
2410 *hp++ = val;
2411 } else {
2412 *hp++ = offset_ptr(val, offs);
2413 }
2414 break;
2415 case TAG_PRIMARY_HEADER:
2416 *hp++ = val;
2417 switch (val & _HEADER_SUBTAG_MASK) {
2418 case ARITYVAL_SUBTAG:
2419 break;
2420 case REF_SUBTAG:
2421 if (!is_magic_ref_thing(fhp - 1))
2422 goto the_default;
2423 case REFC_BINARY_SUBTAG:
2424 case FUN_SUBTAG:
2425 case EXTERNAL_PID_SUBTAG:
2426 case EXTERNAL_PORT_SUBTAG:
2427 case EXTERNAL_REF_SUBTAG:
2428 oh = (struct erl_off_heap_header*) (hp-1);
2429 cpy_sz = thing_arityval(val);
2430 goto cpy_words;
2431 default:
2432 the_default:
2433 cpy_sz = header_arity(val);
2434
2435 cpy_words:
2436 ASSERT(sz >= cpy_sz);
2437 sz -= cpy_sz;
2438 sys_memcpy(hp, fhp, cpy_sz * sizeof(Eterm));
2439 hp += cpy_sz;
2440 fhp += cpy_sz;
2441 if (oh) {
2442 /* Add to offheap list */
2443 oh->next = off_heap->first;
2444 off_heap->first = oh;
2445 ASSERT(*hpp <= (Eterm*)oh);
2446 ASSERT(hp > (Eterm*)oh);
2447 oh = NULL;
2448 }
2449 break;
2450 }
2451 break;
2452 }
2453 }
2454
2455 ASSERT(bp->used_size == hp - *hpp);
2456 *hpp = hp;
2457
2458 for (i = 0; i < nrefs; i++) {
2459 if (is_not_immed(refs[i]) && !erts_is_literal(refs[i],ptr_val(refs[i])))
2460 refs[i] = offset_ptr(refs[i], offs);
2461 }
2462 bp->off_heap.first = NULL;
2463 }
2464
2465 static Uint
setup_rootset(Process * p,Eterm * objv,int nobj,Rootset * rootset)2466 setup_rootset(Process *p, Eterm *objv, int nobj, Rootset *rootset)
2467 {
2468 /*
2469 * NOTE!
2470 * Remember to update offset_rootset() when changing
2471 * this function.
2472 */
2473 Roots* roots;
2474 Uint n;
2475
2476 n = 0;
2477 roots = rootset->roots = rootset->def;
2478 rootset->size = ALENGTH(rootset->def);
2479
2480 roots[n].v = p->stop;
2481 roots[n].sz = STACK_START(p) - p->stop;
2482 ++n;
2483
2484 if (p->dictionary != NULL) {
2485 roots[n].v = ERTS_PD_START(p->dictionary);
2486 roots[n].sz = ERTS_PD_SIZE(p->dictionary);
2487 ++n;
2488 }
2489 if (nobj > 0) {
2490 roots[n].v = objv;
2491 roots[n].sz = nobj;
2492 ++n;
2493 }
2494
2495 ASSERT((is_nil(p->seq_trace_token) ||
2496 is_tuple(follow_moved(p->seq_trace_token, (Eterm) 0)) ||
2497 is_atom(p->seq_trace_token)));
2498 if (is_not_immed(p->seq_trace_token)) {
2499 roots[n].v = &p->seq_trace_token;
2500 roots[n].sz = 1;
2501 n++;
2502 }
2503 #ifdef USE_VM_PROBES
2504 if (is_not_immed(p->dt_utag)) {
2505 roots[n].v = &p->dt_utag;
2506 roots[n].sz = 1;
2507 n++;
2508 }
2509 #endif
2510 ASSERT(IS_TRACER_VALID(ERTS_TRACER(p)));
2511
2512 ASSERT(is_pid(follow_moved(p->group_leader, (Eterm) 0)));
2513 if (is_not_immed(p->group_leader)) {
2514 roots[n].v = &p->group_leader;
2515 roots[n].sz = 1;
2516 n++;
2517 }
2518
2519 /*
2520 * The process may be garbage-collected while it is terminating.
2521 * fvalue contains the EXIT reason.
2522 */
2523 if (is_not_immed(p->fvalue)) {
2524 roots[n].v = &p->fvalue;
2525 roots[n].sz = 1;
2526 n++;
2527 }
2528
2529 /*
2530 * The raise/3 BIF will store the stacktrace in ftrace.
2531 */
2532 if (is_not_immed(p->ftrace)) {
2533 roots[n].v = &p->ftrace;
2534 roots[n].sz = 1;
2535 n++;
2536 }
2537
2538 if (p->sig_qs.recv_mrk_blk) {
2539 roots[n].v = &p->sig_qs.recv_mrk_blk->ref[0];
2540 roots[n].sz = ERTS_RECV_MARKER_BLOCK_SIZE;
2541 n++;
2542 }
2543
2544 /*
2545 * If a NIF or BIF has saved arguments, they need to be added
2546 */
2547 if (erts_setup_nfunc_rootset(p, &roots[n].v, &roots[n].sz))
2548 n++;
2549
2550 ASSERT(n <= rootset->size);
2551
2552 if ((p->sig_qs.flags & (FS_OFF_HEAP_MSGQ
2553 | FS_OFF_HEAP_MSGQ_CHNG)) != FS_OFF_HEAP_MSGQ) {
2554 Uint size;
2555 ErtsMessage *mp;
2556 /*
2557 * We do not have off heap message queue enabled, i.e. we
2558 * need to add signal queues to rootset...
2559 */
2560
2561 #ifdef DEBUG
2562 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ) {
2563 erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
2564 /*
2565 * Verify that we do not have any messages in the outer
2566 * queue that might refer to the heap...
2567 */
2568 for (mp = p->sig_inq.first; mp; mp = mp->next) {
2569 if ((ERTS_SIG_IS_INTERNAL_MSG(mp) && !mp->data.attached)
2570 || ERTS_SIG_IS_HEAP_ALIAS_MSG(mp)) {
2571 int i = ERTS_SIG_IS_INTERNAL_MSG(mp) ? 0 : 1;
2572 for (; i < ERL_MESSAGE_REF_ARRAY_SZ; i++) {
2573 ASSERT(is_immed(mp->m[i])
2574 || erts_is_literal(mp->m[i],
2575 ptr_val(mp->m[i])));
2576 }
2577 }
2578 }
2579 erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
2580 }
2581 #endif
2582
2583 size = n + erts_proc_sig_privqs_len(p);
2584 if (p->sig_qs.cont) {
2585 /*
2586 * We do not know the exact size needed since
2587 * alias-message signals are not included in
2588 * length of private queues. We might have to
2589 * resize while inspecting the signal queue...
2590 */
2591 size += 128;
2592 }
2593 if (size > rootset->size) {
2594 ERTS_GC_ASSERT(roots == rootset->def);
2595 roots = erts_alloc(ERTS_ALC_T_ROOTSET,
2596 size*sizeof(Roots));
2597 sys_memcpy(roots, rootset->def, n*sizeof(Roots));
2598 rootset->size = size;
2599 }
2600
2601 mp = p->sig_qs.first;
2602 while (mp) {
2603 if (ERTS_SIG_IS_INTERNAL_MSG(mp) && !mp->data.attached) {
2604 roots[n].v = mp->m;
2605 roots[n].sz = ERL_MESSAGE_REF_ARRAY_SZ;
2606 n++;
2607 }
2608 mp = mp->next;
2609 }
2610 mp = p->sig_qs.cont;
2611 while (mp) {
2612 if (size <= n) {
2613 Roots *old_roots = roots;
2614 size += 128;
2615 roots = erts_alloc(ERTS_ALC_T_ROOTSET,
2616 size*sizeof(Roots));
2617 sys_memcpy(roots, old_roots, n*sizeof(Roots));
2618 if (old_roots != rootset->def)
2619 erts_free(ERTS_ALC_T_ROOTSET, old_roots);
2620 rootset->size = size;
2621 }
2622 if (ERTS_SIG_IS_INTERNAL_MSG(mp) && !mp->data.attached) {
2623 roots[n].v = mp->m;
2624 roots[n].sz = ERL_MESSAGE_REF_ARRAY_SZ;
2625 n++;
2626 }
2627 else if (ERTS_SIG_IS_HEAP_ALIAS_MSG(mp)) {
2628 /* Exclude message slot... */
2629 roots[n].v = &mp->m[1];
2630 roots[n].sz = ERL_MESSAGE_REF_ARRAY_SZ - 1;
2631 n++;
2632 }
2633 mp = mp->next;
2634 }
2635 }
2636
2637 ASSERT(rootset->size >= n);
2638
2639 rootset->roots = roots;
2640 rootset->num_roots = n;
2641 return n;
2642 }
2643
2644 static
cleanup_rootset(Rootset * rootset)2645 void cleanup_rootset(Rootset* rootset)
2646 {
2647 if (rootset->roots != rootset->def) {
2648 erts_free(ERTS_ALC_T_ROOTSET, rootset->roots);
2649 }
2650 }
2651
2652 static void
grow_new_heap(Process * p,Uint new_sz,Eterm * objv,int nobj)2653 grow_new_heap(Process *p, Uint new_sz, Eterm* objv, int nobj)
2654 {
2655 Eterm* new_heap;
2656 Uint heap_size = HEAP_TOP(p) - HEAP_START(p);
2657 Uint stack_size = p->hend - p->stop;
2658 Sint offs;
2659
2660 ASSERT(HEAP_SIZE(p) < new_sz);
2661 new_heap = (Eterm *) ERTS_HEAP_REALLOC(ERTS_ALC_T_HEAP,
2662 (void *) HEAP_START(p),
2663 sizeof(Eterm)*(HEAP_SIZE(p)),
2664 sizeof(Eterm)*new_sz);
2665
2666 if ((offs = new_heap - HEAP_START(p)) == 0) { /* No move. */
2667 HEAP_END(p) = new_heap + new_sz;
2668 sys_memmove(p->hend - stack_size, p->stop, stack_size * sizeof(Eterm));
2669 p->stop = p->hend - stack_size;
2670 } else {
2671 char* area = (char *) HEAP_START(p);
2672 Uint area_size = (char *) HEAP_TOP(p) - area;
2673 Eterm* prev_stop = p->stop;
2674
2675 offset_heap(new_heap, heap_size, offs, area, area_size);
2676
2677 HIGH_WATER(p) = new_heap + (HIGH_WATER(p) - HEAP_START(p));
2678
2679 HEAP_END(p) = new_heap + new_sz;
2680 prev_stop = new_heap + (p->stop - p->heap);
2681 p->stop = p->hend - stack_size;
2682 sys_memmove(p->stop, prev_stop, stack_size * sizeof(Eterm));
2683
2684 offset_rootset(p, offs, area, area_size, objv, nobj);
2685 HEAP_TOP(p) = new_heap + heap_size;
2686 HEAP_START(p) = new_heap;
2687 }
2688
2689 #ifdef USE_VM_PROBES
2690 if (DTRACE_ENABLED(process_heap_grow)) {
2691 DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
2692
2693 dtrace_proc_str(p, pidbuf);
2694 DTRACE3(process_heap_grow, pidbuf, HEAP_SIZE(p), new_sz);
2695 }
2696 #endif
2697
2698 HEAP_SIZE(p) = new_sz;
2699 }
2700
2701 static void
shrink_new_heap(Process * p,Uint new_sz,Eterm * objv,int nobj)2702 shrink_new_heap(Process *p, Uint new_sz, Eterm *objv, int nobj)
2703 {
2704 Eterm* new_heap;
2705 Uint heap_size = HEAP_TOP(p) - HEAP_START(p);
2706 Sint offs;
2707 Uint stack_size = p->hend - p->stop;
2708
2709 ASSERT(new_sz < p->heap_sz);
2710 sys_memmove(p->heap + new_sz - stack_size, p->stop, stack_size *
2711 sizeof(Eterm));
2712 new_heap = (Eterm *) ERTS_HEAP_REALLOC(ERTS_ALC_T_HEAP,
2713 (void*)p->heap,
2714 sizeof(Eterm)*(HEAP_SIZE(p)),
2715 sizeof(Eterm)*new_sz);
2716 p->hend = new_heap + new_sz;
2717 p->stop = p->hend - stack_size;
2718
2719 if ((offs = new_heap - HEAP_START(p)) != 0) {
2720 char* area = (char *) HEAP_START(p);
2721 Uint area_size = (char *) HEAP_TOP(p) - area;
2722
2723 /*
2724 * Normally, we don't expect a shrunk heap to move, but you never
2725 * know on some strange embedded systems...
2726 */
2727
2728 offset_heap(new_heap, heap_size, offs, area, area_size);
2729
2730 HIGH_WATER(p) = new_heap + (HIGH_WATER(p) - HEAP_START(p));
2731 offset_rootset(p, offs, area, area_size, objv, nobj);
2732 HEAP_TOP(p) = new_heap + heap_size;
2733 HEAP_START(p) = new_heap;
2734 }
2735
2736 #ifdef USE_VM_PROBES
2737 if (DTRACE_ENABLED(process_heap_shrink)) {
2738 DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
2739
2740 dtrace_proc_str(p, pidbuf);
2741 DTRACE3(process_heap_shrink, pidbuf, HEAP_SIZE(p), new_sz);
2742 }
2743 #endif
2744
2745 HEAP_SIZE(p) = new_sz;
2746 }
2747
2748 static Uint64
do_next_vheap_size(Uint64 vheap,Uint64 vheap_sz)2749 do_next_vheap_size(Uint64 vheap, Uint64 vheap_sz) {
2750
2751 /* grow
2752 *
2753 * vheap_sz ======================
2754 *
2755 * vheap 75% + grow
2756 * ----------------------
2757 *
2758 * vheap 25 - 75% same
2759 * ----------------------
2760 *
2761 * vheap ~ - 25% shrink
2762 *
2763 * ----------------------
2764 */
2765
2766 if ((Uint64) vheap/3 > (Uint64) (vheap_sz/4)) {
2767 Uint64 new_vheap_sz = vheap_sz;
2768
2769 while((Uint64) vheap/3 > (Uint64) (vheap_sz/4)) {
2770 /* the golden ratio = 1.618 */
2771 new_vheap_sz = (Uint64) vheap_sz * 1.618;
2772 if (new_vheap_sz < vheap_sz ) {
2773 return vheap_sz;
2774 }
2775 vheap_sz = new_vheap_sz;
2776 }
2777
2778 return vheap_sz;
2779 }
2780
2781 if (vheap < (Uint64) (vheap_sz/4)) {
2782 return (vheap_sz >> 1);
2783 }
2784
2785 return vheap_sz;
2786
2787 }
2788
2789 static Uint64
next_vheap_size(Process * p,Uint64 vheap,Uint64 vheap_sz)2790 next_vheap_size(Process* p, Uint64 vheap, Uint64 vheap_sz) {
2791 Uint64 new_vheap_sz = do_next_vheap_size(vheap, vheap_sz);
2792 return new_vheap_sz < p->min_vheap_size ? p->min_vheap_size : new_vheap_sz;
2793 }
2794
2795 struct shrink_cand_data {
2796 struct erl_off_heap_header* new_candidates;
2797 struct erl_off_heap_header* new_candidates_end;
2798 struct erl_off_heap_header* old_candidates;
2799 Uint no_of_candidates;
2800 Uint no_of_active;
2801 };
2802
2803 static ERTS_INLINE void
link_live_proc_bin(struct shrink_cand_data * shrink,struct erl_off_heap_header *** prevppp,struct erl_off_heap_header ** currpp,int new_heap)2804 link_live_proc_bin(struct shrink_cand_data *shrink,
2805 struct erl_off_heap_header*** prevppp,
2806 struct erl_off_heap_header** currpp,
2807 int new_heap)
2808 {
2809 ProcBin *pbp = (ProcBin*) *currpp;
2810 ASSERT(**prevppp == *currpp);
2811
2812 *currpp = pbp->next;
2813 if (pbp->flags & (PB_ACTIVE_WRITER|PB_IS_WRITABLE)) {
2814 ASSERT(pbp->flags & PB_IS_WRITABLE);
2815
2816 if (pbp->flags & PB_ACTIVE_WRITER) {
2817 pbp->flags &= ~PB_ACTIVE_WRITER;
2818 shrink->no_of_active++;
2819 }
2820 else { /* inactive */
2821 Uint unused = pbp->val->orig_size - pbp->size;
2822 /* Our allocators are 8 byte aligned, i.e., shrinking with
2823 less than 8 bytes will have no real effect */
2824 if (unused >= 8) { /* A shrink candidate; save in candidate list */
2825 **prevppp = pbp->next;
2826 if (new_heap) {
2827 if (!shrink->new_candidates)
2828 shrink->new_candidates_end = (struct erl_off_heap_header*)pbp;
2829 pbp->next = shrink->new_candidates;
2830 shrink->new_candidates = (struct erl_off_heap_header*)pbp;
2831 }
2832 else {
2833 pbp->next = shrink->old_candidates;
2834 shrink->old_candidates = (struct erl_off_heap_header*)pbp;
2835 }
2836 shrink->no_of_candidates++;
2837 return;
2838 }
2839 }
2840 }
2841
2842 /* Not a shrink candidate; keep in original mso list */
2843 *prevppp = &pbp->next;
2844 }
2845
2846 #ifdef ERTS_MAGIC_REF_THING_HEADER
2847 /*
2848 * ERTS_MAGIC_REF_THING_HEADER only defined when there
2849 * is a size difference between magic and ordinary references...
2850 */
2851 # define ERTS_USED_MAGIC_REF_THING_HEADER__ ERTS_MAGIC_REF_THING_HEADER
2852 #else
2853 # define ERTS_USED_MAGIC_REF_THING_HEADER__ ERTS_REF_THING_HEADER
2854 #endif
2855
2856
2857 static void
sweep_off_heap(Process * p,int fullsweep)2858 sweep_off_heap(Process *p, int fullsweep)
2859 {
2860 struct shrink_cand_data shrink = {0};
2861 struct erl_off_heap_header* ptr;
2862 struct erl_off_heap_header** prev;
2863 char* oheap = NULL;
2864 Uint oheap_sz = 0;
2865 Uint64 bin_vheap = 0;
2866 #ifdef DEBUG
2867 int seen_mature = 0;
2868 #endif
2869
2870 if (fullsweep == 0) {
2871 oheap = (char *) OLD_HEAP(p);
2872 oheap_sz = (char *) OLD_HEND(p) - oheap;
2873 }
2874
2875 BIN_OLD_VHEAP(p) = 0;
2876
2877 prev = &MSO(p).first;
2878 ptr = MSO(p).first;
2879
2880 /* First part of the list will reside on the (old) new-heap.
2881 * Keep if moved, otherwise deref.
2882 */
2883 while (ptr) {
2884 if (IS_MOVED_BOXED(ptr->thing_word)) {
2885 ASSERT(!ErtsInArea(ptr, oheap, oheap_sz));
2886 if (is_external_header(((struct erl_off_heap_header*) boxed_val(ptr->thing_word))->thing_word)) {
2887 erts_node_bookkeep(((ExternalThing*)ptr)->node,
2888 make_boxed(&ptr->thing_word),
2889 ERL_NODE_DEC, __FILE__, __LINE__);
2890 }
2891 *prev = ptr = (struct erl_off_heap_header*) boxed_val(ptr->thing_word);
2892 ASSERT(!IS_MOVED_BOXED(ptr->thing_word));
2893 switch (ptr->thing_word) {
2894 case HEADER_PROC_BIN: {
2895 int to_new_heap = !ErtsInArea(ptr, oheap, oheap_sz);
2896 ASSERT(to_new_heap == !seen_mature || (!to_new_heap && (seen_mature=1)));
2897 if (to_new_heap) {
2898 bin_vheap += ptr->size / sizeof(Eterm);
2899 } else {
2900 BIN_OLD_VHEAP(p) += ptr->size / sizeof(Eterm); /* for binary gc (words)*/
2901 }
2902 link_live_proc_bin(&shrink, &prev, &ptr, to_new_heap);
2903 break;
2904 }
2905 case ERTS_USED_MAGIC_REF_THING_HEADER__: {
2906 Uint size;
2907 int to_new_heap = !ErtsInArea(ptr, oheap, oheap_sz);
2908 ASSERT(is_magic_ref_thing(ptr));
2909 ASSERT(to_new_heap == !seen_mature || (!to_new_heap && (seen_mature=1)));
2910 size = (Uint) ((ErtsMRefThing *) ptr)->mb->orig_size;
2911 if (to_new_heap)
2912 bin_vheap += size / sizeof(Eterm);
2913 else
2914 BIN_OLD_VHEAP(p) += size / sizeof(Eterm); /* for binary gc (words)*/
2915 /* fall through... */
2916 }
2917 default:
2918 if (is_external_header(ptr->thing_word)) {
2919 erts_node_bookkeep(((ExternalThing*)ptr)->node,
2920 make_boxed(&ptr->thing_word),
2921 ERL_NODE_INC, __FILE__, __LINE__);
2922 }
2923 prev = &ptr->next;
2924 ptr = ptr->next;
2925 }
2926 }
2927 else if (ErtsInArea(ptr, oheap, oheap_sz))
2928 break; /* and let old-heap loop continue */
2929 else {
2930 /* garbage */
2931 switch (thing_subtag(ptr->thing_word)) {
2932 case REFC_BINARY_SUBTAG:
2933 {
2934 Binary* bptr = ((ProcBin*)ptr)->val;
2935 erts_bin_release(bptr);
2936 break;
2937 }
2938 case FUN_SUBTAG:
2939 {
2940 ErlFunEntry* fe = ((ErlFunThing*)ptr)->fe;
2941 if (erts_refc_dectest(&fe->refc, 0) == 0) {
2942 erts_erase_fun_entry(fe);
2943 }
2944 break;
2945 }
2946 case REF_SUBTAG:
2947 {
2948 ErtsMagicBinary *bptr;
2949 ASSERT(is_magic_ref_thing(ptr));
2950 bptr = ((ErtsMRefThing *) ptr)->mb;
2951 erts_bin_release((Binary *) bptr);
2952 break;
2953 }
2954 default:
2955 ASSERT(is_external_header(ptr->thing_word));
2956 erts_deref_node_entry(((ExternalThing*)ptr)->node,
2957 make_boxed(&ptr->thing_word));
2958 }
2959 *prev = ptr = ptr->next;
2960 }
2961 }
2962
2963 /* The rest of the list resides on old-heap, and we just did a
2964 * generational collection - keep objects in list.
2965 */
2966 while (ptr) {
2967 ASSERT(ErtsInArea(ptr, oheap, oheap_sz));
2968 ASSERT(!IS_MOVED_BOXED(ptr->thing_word));
2969 switch (ptr->thing_word) {
2970 case HEADER_PROC_BIN:
2971 BIN_OLD_VHEAP(p) += ptr->size / sizeof(Eterm); /* for binary gc (words)*/
2972 link_live_proc_bin(&shrink, &prev, &ptr, 0);
2973 break;
2974 case ERTS_USED_MAGIC_REF_THING_HEADER__:
2975 ASSERT(is_magic_ref_thing(ptr));
2976 BIN_OLD_VHEAP(p) +=
2977 (((Uint) ((ErtsMRefThing *) ptr)->mb->orig_size)
2978 / sizeof(Eterm)); /* for binary gc (words)*/
2979 /* fall through... */
2980 default:
2981 ASSERT(is_fun_header(ptr->thing_word) ||
2982 is_external_header(ptr->thing_word)
2983 || is_magic_ref_thing(ptr));
2984 prev = &ptr->next;
2985 ptr = ptr->next;
2986 break;
2987 }
2988 }
2989
2990 if (fullsweep) {
2991 BIN_OLD_VHEAP_SZ(p) = next_vheap_size(p, BIN_OLD_VHEAP(p) + MSO(p).overhead, BIN_OLD_VHEAP_SZ(p));
2992 }
2993 BIN_VHEAP_SZ(p) = next_vheap_size(p, bin_vheap, BIN_VHEAP_SZ(p));
2994 MSO(p).overhead = bin_vheap;
2995
2996 /*
2997 * If we got any shrink candidates, check them out.
2998 */
2999
3000 if (shrink.no_of_candidates) {
3001 ProcBin *candlist[] = { (ProcBin*)shrink.new_candidates,
3002 (ProcBin*)shrink.old_candidates };
3003 Uint leave_unused = 0;
3004 int i;
3005
3006 if (shrink.no_of_active == 0) {
3007 if (shrink.no_of_candidates <= ERTS_INACT_WR_PB_LEAVE_MUCH_LIMIT)
3008 leave_unused = ERTS_INACT_WR_PB_LEAVE_MUCH_PERCENTAGE;
3009 else if (shrink.no_of_candidates <= ERTS_INACT_WR_PB_LEAVE_LIMIT)
3010 leave_unused = ERTS_INACT_WR_PB_LEAVE_PERCENTAGE;
3011 }
3012
3013 for (i = 0; i < sizeof(candlist)/sizeof(candlist[0]); i++) {
3014 ProcBin* pb;
3015 for (pb = candlist[i]; pb; pb = (ProcBin*)pb->next) {
3016 Uint new_size = pb->size;
3017
3018 if (leave_unused) {
3019 new_size += (new_size * 100) / leave_unused;
3020 /* Our allocators are 8 byte aligned, i.e., shrinking with
3021 less than 8 bytes will have no real effect */
3022 if (new_size + 8 >= pb->val->orig_size)
3023 continue;
3024 }
3025
3026 pb->val = erts_bin_realloc(pb->val, new_size);
3027 pb->bytes = (byte *) pb->val->orig_bytes;
3028 }
3029 }
3030
3031
3032 /*
3033 * We now potentially have the mso list divided into three lists:
3034 * - shrink candidates on new heap (inactive writable with unused data)
3035 * - shrink candidates on old heap (inactive writable with unused data)
3036 * - other binaries (read only + active writable ...) + funs and externals
3037 *
3038 * Put them back together: new candidates -> other -> old candidates
3039 * This order will ensure that the list only refers from new
3040 * generation to old and never from old to new *which is important*.
3041 */
3042 if (shrink.new_candidates) {
3043 if (prev == &MSO(p).first) /* empty other binaries list */
3044 prev = &shrink.new_candidates_end->next;
3045 else
3046 shrink.new_candidates_end->next = MSO(p).first;
3047 MSO(p).first = shrink.new_candidates;
3048 }
3049 }
3050 *prev = shrink.old_candidates;
3051 }
3052
3053 /*
3054 * Offset pointers into the heap (not stack).
3055 */
3056
3057 static void
offset_heap(Eterm * hp,Uint sz,Sint offs,char * area,Uint area_size)3058 offset_heap(Eterm* hp, Uint sz, Sint offs, char* area, Uint area_size)
3059 {
3060 while (sz--) {
3061 Eterm val = *hp;
3062 switch (primary_tag(val)) {
3063 case TAG_PRIMARY_LIST:
3064 case TAG_PRIMARY_BOXED:
3065 if (ErtsInArea(ptr_val(val), area, area_size)) {
3066 *hp = offset_ptr(val, offs);
3067 }
3068 hp++;
3069 break;
3070 case TAG_PRIMARY_HEADER: {
3071 Uint tari;
3072
3073 if (header_is_transparent(val)) {
3074 hp++;
3075 continue;
3076 }
3077 tari = thing_arityval(val);
3078 switch (thing_subtag(val)) {
3079 case REF_SUBTAG:
3080 if (!is_magic_ref_thing(hp))
3081 break;
3082 case REFC_BINARY_SUBTAG:
3083 case FUN_SUBTAG:
3084 case EXTERNAL_PID_SUBTAG:
3085 case EXTERNAL_PORT_SUBTAG:
3086 case EXTERNAL_REF_SUBTAG:
3087 {
3088 struct erl_off_heap_header* oh = (struct erl_off_heap_header*) hp;
3089
3090 if (is_external_header(oh->thing_word)) {
3091 erts_node_bookkeep(((ExternalThing*)oh)->node,
3092 make_boxed(((Eterm*)oh)-offs),
3093 ERL_NODE_DEC, __FILE__, __LINE__);
3094 erts_node_bookkeep(((ExternalThing*)oh)->node,
3095 make_boxed((Eterm*)oh), ERL_NODE_INC,
3096 __FILE__, __LINE__);
3097 }
3098
3099 if (ErtsInArea(oh->next, area, area_size)) {
3100 Eterm** uptr = (Eterm **) (void *) &oh->next;
3101 *uptr += offs; /* Patch the mso chain */
3102 }
3103 }
3104 break;
3105 case BIN_MATCHSTATE_SUBTAG:
3106 {
3107 ErlBinMatchState *ms = (ErlBinMatchState*) hp;
3108 ErlBinMatchBuffer *mb = &(ms->mb);
3109 if (ErtsInArea(ptr_val(mb->orig), area, area_size)) {
3110 mb->orig = offset_ptr(mb->orig, offs);
3111 mb->base = binary_bytes(mb->orig);
3112 }
3113 }
3114 break;
3115 }
3116 sz -= tari;
3117 hp += tari + 1;
3118 break;
3119 }
3120 default:
3121 hp++;
3122 continue;
3123 }
3124 }
3125 }
3126
3127 /*
3128 * Offset pointers to heap from stack.
3129 */
3130
3131 static void
offset_heap_ptr(Eterm * hp,Uint sz,Sint offs,char * area,Uint area_size)3132 offset_heap_ptr(Eterm* hp, Uint sz, Sint offs, char* area, Uint area_size)
3133 {
3134 while (sz--) {
3135 Eterm val = *hp;
3136 switch (primary_tag(val)) {
3137 case TAG_PRIMARY_LIST:
3138 case TAG_PRIMARY_BOXED:
3139 if (ErtsInArea(ptr_val(val), area, area_size)) {
3140 *hp = offset_ptr(val, offs);
3141 }
3142 hp++;
3143 break;
3144 default:
3145 hp++;
3146 break;
3147 }
3148 }
3149 }
3150
3151 static void
offset_off_heap(Process * p,Sint offs,char * area,Uint area_size)3152 offset_off_heap(Process* p, Sint offs, char* area, Uint area_size)
3153 {
3154 if (MSO(p).first && ErtsInArea((Eterm *)MSO(p).first, area, area_size)) {
3155 Eterm** uptr = (Eterm**) (void *) &MSO(p).first;
3156 *uptr += offs;
3157 }
3158 }
3159
3160 #ifndef USE_VM_PROBES
3161 #define ERTS_OFFSET_DT_UTAG(MP, A, ASZ, O)
3162 #else
3163 #define ERTS_OFFSET_DT_UTAG(MP, A, ASZ, O) \
3164 do { \
3165 Eterm utag__ = ERL_MESSAGE_DT_UTAG((MP)); \
3166 if (is_boxed(utag__) && ErtsInArea(ptr_val(utag__), (A), (ASZ))) { \
3167 ERL_MESSAGE_DT_UTAG((MP)) = offset_ptr(utag__, (O)); \
3168 } \
3169 } while (0)
3170 #endif
3171
3172 static ERTS_INLINE void
offset_message(ErtsMessage * mp,Sint offs,char * area,Uint area_size)3173 offset_message(ErtsMessage *mp, Sint offs, char* area, Uint area_size)
3174 {
3175 Eterm mesg = ERL_MESSAGE_TERM(mp);
3176 if (ERTS_SIG_IS_MSG_TAG(mesg) || ERTS_SIG_IS_HEAP_ALIAS_MSG_TAG(mesg)) {
3177 if (ERTS_SIG_IS_INTERNAL_MSG_TAG(mesg)) {
3178 switch (primary_tag(mesg)) {
3179 case TAG_PRIMARY_LIST:
3180 case TAG_PRIMARY_BOXED:
3181 if (ErtsInArea(ptr_val(mesg), area, area_size)) {
3182 ERL_MESSAGE_TERM(mp) = offset_ptr(mesg, offs);
3183 }
3184 break;
3185 }
3186 }
3187 mesg = ERL_MESSAGE_TOKEN(mp);
3188 if (is_boxed(mesg) && ErtsInArea(ptr_val(mesg), area, area_size)) {
3189 ERL_MESSAGE_TOKEN(mp) = offset_ptr(mesg, offs);
3190 }
3191 mesg = ERL_MESSAGE_FROM(mp);
3192 if (is_boxed(mesg) && ErtsInArea(ptr_val(mesg), area, area_size)) {
3193 ERL_MESSAGE_FROM(mp) = offset_ptr(mesg, offs);
3194 }
3195
3196 ERTS_OFFSET_DT_UTAG(mp, area, area_size, offs);
3197
3198 ASSERT((is_nil(ERL_MESSAGE_TOKEN(mp)) ||
3199 is_tuple(ERL_MESSAGE_TOKEN(mp)) ||
3200 is_atom(ERL_MESSAGE_TOKEN(mp))));
3201 }
3202 }
3203
3204 /*
3205 * Offset pointers in message queue.
3206 */
3207 static void
offset_mqueue(Process * p,Sint offs,char * area,Uint area_size)3208 offset_mqueue(Process *p, Sint offs, char* area, Uint area_size)
3209 {
3210 if ((p->sig_qs.flags & (FS_OFF_HEAP_MSGQ|FS_OFF_HEAP_MSGQ_CHNG)) != FS_OFF_HEAP_MSGQ)
3211 ERTS_FOREACH_SIG_PRIVQS(p, mp, offset_message(mp, offs, area, area_size));
3212 }
3213
3214 static void ERTS_INLINE
offset_one_rootset(Process * p,Sint offs,char * area,Uint area_size,Eterm * objv,int nobj)3215 offset_one_rootset(Process *p, Sint offs, char* area, Uint area_size,
3216 Eterm* objv, int nobj)
3217 {
3218 Eterm *v;
3219 Uint sz;
3220 if (p->dictionary) {
3221 offset_heap(ERTS_PD_START(p->dictionary),
3222 ERTS_PD_SIZE(p->dictionary),
3223 offs, area, area_size);
3224 }
3225
3226 offset_heap_ptr(&p->fvalue, 1, offs, area, area_size);
3227 offset_heap_ptr(&p->ftrace, 1, offs, area, area_size);
3228 offset_heap_ptr(&p->seq_trace_token, 1, offs, area, area_size);
3229 #ifdef USE_VM_PROBES
3230 offset_heap_ptr(&p->dt_utag, 1, offs, area, area_size);
3231 #endif
3232 offset_heap_ptr(&p->group_leader, 1, offs, area, area_size);
3233 if (p->sig_qs.recv_mrk_blk)
3234 offset_heap_ptr(&p->sig_qs.recv_mrk_blk->ref[0],
3235 ERTS_RECV_MARKER_BLOCK_SIZE, offs, area, area_size);
3236 offset_mqueue(p, offs, area, area_size);
3237 offset_heap_ptr(p->stop, (STACK_START(p) - p->stop), offs, area, area_size);
3238 offset_nstack(p, offs, area, area_size);
3239 if (nobj > 0) {
3240 offset_heap_ptr(objv, nobj, offs, area, area_size);
3241 }
3242 offset_off_heap(p, offs, area, area_size);
3243 if (erts_setup_nfunc_rootset(p, &v, &sz))
3244 offset_heap_ptr(v, sz, offs, area, area_size);
3245 }
3246
3247 static void
offset_rootset(Process * p,Sint offs,char * area,Uint area_size,Eterm * objv,int nobj)3248 offset_rootset(Process *p, Sint offs, char* area, Uint area_size,
3249 Eterm* objv, int nobj)
3250 {
3251 offset_one_rootset(p, offs, area, area_size, objv, nobj);
3252 }
3253
3254 static void
init_gc_info(ErtsGCInfo * gcip)3255 init_gc_info(ErtsGCInfo *gcip)
3256 {
3257 gcip->reclaimed = 0;
3258 gcip->garbage_cols = 0;
3259 }
3260
3261 static void
reply_gc_info(void * vgcirp)3262 reply_gc_info(void *vgcirp)
3263 {
3264 Uint64 reclaimed = 0, garbage_cols = 0;
3265 ErtsSchedulerData *esdp = erts_get_scheduler_data();
3266 ErtsGCInfoReq *gcirp = (ErtsGCInfoReq *) vgcirp;
3267 ErtsProcLocks rp_locks = (gcirp->req_sched == esdp->no
3268 ? ERTS_PROC_LOCK_MAIN
3269 : 0);
3270 Process *rp = gcirp->proc;
3271 Eterm ref_copy = NIL, msg;
3272 Eterm *hp = NULL;
3273 Eterm **hpp;
3274 Uint sz, *szp;
3275 ErlOffHeap *ohp = NULL;
3276 ErtsMessage *mp = NULL;
3277
3278 ASSERT(esdp);
3279
3280 reclaimed = esdp->gc_info.reclaimed;
3281 garbage_cols = esdp->gc_info.garbage_cols;
3282 /*
3283 * Add dirty schedulers info on requesting
3284 * schedulers info
3285 */
3286 if (gcirp->req_sched == esdp->no) {
3287 erts_mtx_lock(&dirty_gc.mtx);
3288 reclaimed += dirty_gc.info.reclaimed;
3289 garbage_cols += dirty_gc.info.garbage_cols;
3290 erts_mtx_unlock(&dirty_gc.mtx);
3291 }
3292
3293 sz = 0;
3294 hpp = NULL;
3295 szp = &sz;
3296
3297 while (1) {
3298 if (hpp)
3299 ref_copy = STORE_NC(hpp, ohp, gcirp->ref);
3300 else
3301 *szp += ERTS_REF_THING_SIZE;
3302
3303 msg = erts_bld_tuple(hpp, szp, 3,
3304 make_small(esdp->no),
3305 erts_bld_uint64(hpp, szp, garbage_cols),
3306 erts_bld_uint64(hpp, szp, reclaimed));
3307
3308 msg = erts_bld_tuple(hpp, szp, 2, ref_copy, msg);
3309 if (hpp)
3310 break;
3311
3312 mp = erts_alloc_message_heap(rp, &rp_locks, sz, &hp, &ohp);
3313
3314 szp = NULL;
3315 hpp = &hp;
3316 }
3317
3318 erts_queue_message(rp, rp_locks, mp, msg, am_system);
3319
3320 if (gcirp->req_sched == esdp->no)
3321 rp_locks &= ~ERTS_PROC_LOCK_MAIN;
3322
3323 if (rp_locks)
3324 erts_proc_unlock(rp, rp_locks);
3325
3326 erts_proc_dec_refc(rp);
3327
3328 if (erts_atomic32_dec_read_nob(&gcirp->refc) == 0)
3329 gcireq_free(vgcirp);
3330 }
3331
erts_sub_binary_to_heap_binary(Eterm * ptr,Eterm ** hpp,Eterm * orig)3332 Eterm* erts_sub_binary_to_heap_binary(Eterm *ptr, Eterm **hpp, Eterm *orig) {
3333 Eterm *htop = *hpp;
3334 Eterm gval;
3335 ErlSubBin *sb = (ErlSubBin *)ptr;
3336 ErlHeapBin *hb = (ErlHeapBin *)htop;
3337 Eterm *real_bin;
3338 byte *bs;
3339
3340 real_bin = binary_val(follow_moved(sb->orig, (Eterm)0));
3341
3342 if (*real_bin == HEADER_PROC_BIN) {
3343 bs = ((ProcBin *) real_bin)->bytes + sb->offs;
3344 } else {
3345 bs = (byte *)(&(((ErlHeapBin *) real_bin)->data)) + sb->offs;
3346 }
3347
3348 hb->thing_word = header_heap_bin(sb->size);
3349 hb->size = sb->size;
3350 sys_memcpy((byte *)hb->data, bs, sb->size);
3351
3352 gval = make_boxed(htop);
3353 *orig = gval;
3354 *ptr = gval;
3355
3356 ptr += ERL_SUB_BIN_SIZE;
3357 htop += heap_bin_size(sb->size);
3358
3359 *hpp = htop;
3360 return ptr;
3361 }
3362
3363
3364 Eterm
erts_gc_info_request(Process * c_p)3365 erts_gc_info_request(Process *c_p)
3366 {
3367 ErtsSchedulerData *esdp = erts_proc_sched_data(c_p);
3368 Eterm ref;
3369 ErtsGCInfoReq *gcirp;
3370 Eterm *hp;
3371
3372 gcirp = gcireq_alloc();
3373 ref = erts_make_ref(c_p);
3374 hp = &gcirp->ref_heap[0];
3375
3376 gcirp->proc = c_p;
3377 gcirp->ref = STORE_NC(&hp, NULL, ref);
3378 gcirp->req_sched = esdp->no;
3379 erts_atomic32_init_nob(&gcirp->refc,
3380 (erts_aint32_t) erts_no_schedulers);
3381
3382 erts_proc_add_refc(c_p, (Sint) erts_no_schedulers);
3383
3384 if (erts_no_schedulers > 1)
3385 erts_schedule_multi_misc_aux_work(1,
3386 erts_no_schedulers,
3387 reply_gc_info,
3388 (void *) gcirp);
3389
3390 reply_gc_info((void *) gcirp);
3391
3392 return ref;
3393 }
3394
3395 Eterm
erts_process_gc_info(Process * p,Uint * sizep,Eterm ** hpp,Uint extra_heap_block,Uint extra_old_heap_block_size)3396 erts_process_gc_info(Process *p, Uint *sizep, Eterm **hpp,
3397 Uint extra_heap_block,
3398 Uint extra_old_heap_block_size)
3399 {
3400 ERTS_DECL_AM(bin_vheap_size);
3401 ERTS_DECL_AM(bin_vheap_block_size);
3402 ERTS_DECL_AM(bin_old_vheap_size);
3403 ERTS_DECL_AM(bin_old_vheap_block_size);
3404 Eterm tags[] = {
3405 /* If you increase the number of elements here, make sure to update
3406 any call sites as they may have stack allocations that depend
3407 on the number of elements here. */
3408 am_old_heap_block_size,
3409 am_heap_block_size,
3410 am_mbuf_size,
3411 am_recent_size,
3412 am_stack_size,
3413 am_old_heap_size,
3414 am_heap_size,
3415 AM_bin_vheap_size,
3416 AM_bin_vheap_block_size,
3417 AM_bin_old_vheap_size,
3418 AM_bin_old_vheap_block_size
3419 };
3420 UWord values[] = {
3421 OLD_HEAP(p) ? OLD_HEND(p) - OLD_HEAP(p) + extra_old_heap_block_size
3422 : extra_old_heap_block_size,
3423 HEAP_SIZE(p) + extra_heap_block,
3424 MBUF_SIZE(p),
3425 HIGH_WATER(p) - HEAP_START(p),
3426 STACK_START(p) - p->stop,
3427 OLD_HEAP(p) ? OLD_HTOP(p) - OLD_HEAP(p) : 0,
3428 HEAP_TOP(p) - HEAP_START(p),
3429 MSO(p).overhead,
3430 BIN_VHEAP_SZ(p),
3431 BIN_OLD_VHEAP(p),
3432 BIN_OLD_VHEAP_SZ(p)
3433 };
3434
3435 Eterm res = THE_NON_VALUE;
3436
3437 ERTS_CT_ASSERT(sizeof(values)/sizeof(*values) == sizeof(tags)/sizeof(*tags));
3438 ERTS_CT_ASSERT(sizeof(values)/sizeof(*values) == ERTS_PROCESS_GC_INFO_MAX_TERMS);
3439
3440 if (p->abandoned_heap) {
3441 Eterm *htop, *heap;
3442 ERTS_GET_ORIG_HEAP(p, heap, htop);
3443 values[3] = HIGH_WATER(p) - heap;
3444 values[6] = htop - heap;
3445 }
3446
3447 if (p->sig_qs.flags & FS_ON_HEAP_MSGQ) {
3448 /* If on heap messages in the internal queue are counted
3449 as being part of the heap, so we have to add them to the
3450 am_mbuf_size value. process_info(total_heap_size) should
3451 be the same as adding old_heap_block_size + heap_block_size
3452 + mbuf_size.
3453 */
3454 ERTS_FOREACH_SIG_PRIVQS(
3455 p, mp,
3456 {
3457 if (ERTS_SIG_IS_MSG(mp)
3458 && mp->data.attached
3459 && mp->data.attached != ERTS_MSG_COMBINED_HFRAG) {
3460 values[2] += erts_msg_attached_data_size(mp);
3461 }
3462 });
3463 }
3464
3465 res = erts_bld_atom_uword_2tup_list(hpp,
3466 sizep,
3467 sizeof(values)/sizeof(*values),
3468 tags,
3469 values);
3470
3471 return res;
3472 }
3473
3474 static int
reached_max_heap_size(Process * p,Uint total_heap_size,Uint extra_heap_size,Uint extra_old_heap_size)3475 reached_max_heap_size(Process *p, Uint total_heap_size,
3476 Uint extra_heap_size, Uint extra_old_heap_size)
3477 {
3478 Uint max_heap_flags = MAX_HEAP_SIZE_FLAGS_GET(p);
3479 if (IS_TRACED_FL(p, F_TRACE_GC) ||
3480 max_heap_flags & MAX_HEAP_SIZE_LOG) {
3481 Eterm msg;
3482 Uint size = 0;
3483 Eterm *o_hp , *hp;
3484 erts_process_gc_info(p, &size, NULL, extra_heap_size,
3485 extra_old_heap_size);
3486 o_hp = hp = erts_alloc(ERTS_ALC_T_TMP, size * sizeof(Eterm));
3487 msg = erts_process_gc_info(p, NULL, &hp, extra_heap_size,
3488 extra_old_heap_size);
3489
3490 if (max_heap_flags & MAX_HEAP_SIZE_LOG) {
3491 int alive = erts_is_alive;
3492 erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
3493 Eterm *o_hp, *hp, args = NIL;
3494
3495 /* Build the format message */
3496 erts_dsprintf(dsbufp, " Process: ~p ");
3497 if (alive)
3498 erts_dsprintf(dsbufp, "on node ~p");
3499 erts_dsprintf(dsbufp, "~n Context: maximum heap size reached~n");
3500 erts_dsprintf(dsbufp, " Max Heap Size: ~p~n");
3501 erts_dsprintf(dsbufp, " Total Heap Size: ~p~n");
3502 erts_dsprintf(dsbufp, " Kill: ~p~n");
3503 erts_dsprintf(dsbufp, " Error Logger: ~p~n");
3504 erts_dsprintf(dsbufp, " Message Queue Len: ~p~n");
3505 erts_dsprintf(dsbufp, " GC Info: ~p~n");
3506
3507 /* Build the args in reverse order */
3508 o_hp = hp = erts_alloc(ERTS_ALC_T_TMP, 2*(alive ? 8 : 7) * sizeof(Eterm));
3509 args = CONS(hp, msg, args); hp += 2;
3510 args = CONS(hp, make_small((p)->sig_inq.len), args); hp += 2;
3511 args = CONS(hp, am_true, args); hp += 2;
3512 args = CONS(hp, (max_heap_flags & MAX_HEAP_SIZE_KILL ? am_true : am_false), args); hp += 2;
3513 args = CONS(hp, make_small(total_heap_size), args); hp += 2;
3514 args = CONS(hp, make_small(MAX_HEAP_SIZE_GET(p)), args); hp += 2;
3515 if (alive) {
3516 args = CONS(hp, erts_this_node->sysname, args); hp += 2;
3517 }
3518 args = CONS(hp, p->common.id, args); hp += 2;
3519
3520 erts_send_error_term_to_logger(p->group_leader, dsbufp, args);
3521 erts_free(ERTS_ALC_T_TMP, o_hp);
3522 }
3523
3524 if (IS_TRACED_FL(p, F_TRACE_GC))
3525 trace_gc(p, am_gc_max_heap_size, 0, msg);
3526
3527 erts_free(ERTS_ALC_T_TMP, o_hp);
3528 }
3529 /* returns true if we should kill the process */
3530 return max_heap_flags & MAX_HEAP_SIZE_KILL;
3531 }
3532
3533 Eterm
erts_max_heap_size_map(Sint max_heap_size,Uint max_heap_flags,Eterm ** hpp,Uint * sz)3534 erts_max_heap_size_map(Sint max_heap_size, Uint max_heap_flags,
3535 Eterm **hpp, Uint *sz)
3536 {
3537 if (!hpp) {
3538 *sz += ERTS_MAX_HEAP_SIZE_MAP_SZ;
3539 return THE_NON_VALUE;
3540 } else {
3541 Eterm *hp = *hpp;
3542 Eterm keys = TUPLE3(hp, am_error_logger, am_kill, am_size);
3543 flatmap_t *mp;
3544 hp += 4;
3545 mp = (flatmap_t*) hp;
3546 mp->thing_word = MAP_HEADER_FLATMAP;
3547 mp->size = 3;
3548 mp->keys = keys;
3549 hp += MAP_HEADER_FLATMAP_SZ;
3550 *hp++ = max_heap_flags & MAX_HEAP_SIZE_LOG ? am_true : am_false;
3551 *hp++ = max_heap_flags & MAX_HEAP_SIZE_KILL ? am_true : am_false;
3552 *hp++ = make_small(max_heap_size);
3553 *hpp = hp;
3554 return make_flatmap(mp);
3555 }
3556 }
3557
3558 int
erts_max_heap_size(Eterm arg,Uint * max_heap_size,Uint * max_heap_flags)3559 erts_max_heap_size(Eterm arg, Uint *max_heap_size, Uint *max_heap_flags)
3560 {
3561 Sint sz;
3562 *max_heap_flags = H_MAX_FLAGS;
3563 if (is_small(arg)) {
3564 sz = signed_val(arg);
3565 *max_heap_flags = H_MAX_FLAGS;
3566 } else if (is_map(arg)) {
3567 const Eterm *size = erts_maps_get(am_size, arg);
3568 const Eterm *kill = erts_maps_get(am_kill, arg);
3569 const Eterm *log = erts_maps_get(am_error_logger, arg);
3570 if (size && is_small(*size)) {
3571 sz = signed_val(*size);
3572 } else {
3573 /* size is mandatory */
3574 return 0;
3575 }
3576 if (kill) {
3577 if (*kill == am_true)
3578 *max_heap_flags |= MAX_HEAP_SIZE_KILL;
3579 else if (*kill == am_false)
3580 *max_heap_flags &= ~MAX_HEAP_SIZE_KILL;
3581 else
3582 return 0;
3583 }
3584 if (log) {
3585 if (*log == am_true)
3586 *max_heap_flags |= MAX_HEAP_SIZE_LOG;
3587 else if (*log == am_false)
3588 *max_heap_flags &= ~MAX_HEAP_SIZE_LOG;
3589 else
3590 return 0;
3591 }
3592 } else
3593 return 0;
3594 if (sz < 0)
3595 return 0;
3596 *max_heap_size = sz;
3597 return 1;
3598 }
3599
3600 #if defined(DEBUG) || defined(ERTS_OFFHEAP_DEBUG)
3601
3602 int
erts_dbg_within_proc(Eterm * ptr,Process * p,Eterm * real_htop)3603 erts_dbg_within_proc(Eterm *ptr, Process *p, Eterm *real_htop)
3604 {
3605 ErlHeapFragment* bp;
3606 ErtsMessage* mp;
3607 Eterm *htop, *heap;
3608
3609 if (p->abandoned_heap) {
3610 ERTS_GET_ORIG_HEAP(p, heap, htop);
3611 if (heap <= ptr && ptr < htop)
3612 return 1;
3613 }
3614
3615 heap = p->heap;
3616 htop = real_htop ? real_htop : HEAP_TOP(p);
3617
3618 if (OLD_HEAP(p) && (OLD_HEAP(p) <= ptr && ptr < OLD_HEND(p))) {
3619 return 1;
3620 }
3621 if (heap <= ptr && ptr < htop) {
3622 return 1;
3623 }
3624
3625 mp = p->msg_frag;
3626 bp = p->mbuf;
3627
3628 if (bp)
3629 goto search_heap_frags;
3630
3631 while (mp) {
3632
3633 bp = erts_message_to_heap_frag(mp);
3634 mp = mp->next;
3635
3636 search_heap_frags:
3637
3638 while (bp) {
3639 if (bp->mem <= ptr && ptr < bp->mem + bp->used_size) {
3640 return 1;
3641 }
3642 bp = bp->next;
3643 }
3644 }
3645
3646 return 0;
3647 }
3648
3649 #endif
3650
3651 #ifdef ERTS_OFFHEAP_DEBUG
3652
3653 #define ERTS_CHK_OFFHEAP_ASSERT(EXP) \
3654 do { \
3655 if (!(EXP)) \
3656 erts_exit(ERTS_ABORT_EXIT, \
3657 "%s:%d: Assertion failed: %s\n", \
3658 __FILE__, __LINE__, #EXP); \
3659 } while (0)
3660
3661
3662 #ifdef ERTS_OFFHEAP_DEBUG_CHK_CIRCULAR_LIST
3663 # define ERTS_OFFHEAP_VISITED_BIT ((Eterm) 1 << 31)
3664 #endif
3665
3666 void
erts_check_off_heap2(Process * p,Eterm * htop)3667 erts_check_off_heap2(Process *p, Eterm *htop)
3668 {
3669 Eterm *oheap = (Eterm *) OLD_HEAP(p);
3670 Eterm *ohtop = (Eterm *) OLD_HTOP(p);
3671 int old;
3672 union erl_off_heap_ptr u;
3673
3674 old = 0;
3675 for (u.hdr = MSO(p).first; u.hdr; u.hdr = u.hdr->next) {
3676 erts_aint_t refc;
3677 switch (thing_subtag(u.hdr->thing_word)) {
3678 case REFC_BINARY_SUBTAG:
3679 refc = erts_refc_read(&u.pb->val->intern.refc, 1);
3680 break;
3681 case FUN_SUBTAG:
3682 refc = erts_refc_read(&u.fun->fe->refc, 1);
3683 break;
3684 case EXTERNAL_PID_SUBTAG:
3685 case EXTERNAL_PORT_SUBTAG:
3686 case EXTERNAL_REF_SUBTAG:
3687 refc = erts_refc_read(&u.ext->node->refc, 1);
3688 break;
3689 case REF_SUBTAG:
3690 ASSERT(is_magic_ref_thing(u.hdr));
3691 refc = erts_refc_read(&u.mref->mb->intern.refc, 1);
3692 break;
3693 default:
3694 ASSERT(!"erts_check_off_heap2: Invalid thing_word");
3695 }
3696 ERTS_CHK_OFFHEAP_ASSERT(refc >= 1);
3697 #ifdef ERTS_OFFHEAP_DEBUG_CHK_CIRCULAR_LIST
3698 ERTS_CHK_OFFHEAP_ASSERT(!(u.hdr->thing_word & ERTS_OFFHEAP_VISITED_BIT));
3699 u.hdr->thing_word |= ERTS_OFFHEAP_VISITED_BIT;
3700 #endif
3701 if (old) {
3702 ERTS_CHK_OFFHEAP_ASSERT(oheap <= u.ep && u.ep < ohtop);
3703 }
3704 else if (oheap <= u.ep && u.ep < ohtop)
3705 old = 1;
3706 else {
3707 ERTS_CHK_OFFHEAP_ASSERT(erts_dbg_within_proc(u.ep, p, htop));
3708 }
3709 }
3710
3711 #ifdef ERTS_OFFHEAP_DEBUG_CHK_CIRCULAR_LIST
3712 for (u.hdr = MSO(p).first; u.hdr; u.hdr = u.hdr->next)
3713 u.hdr->thing_word &= ~ERTS_OFFHEAP_VISITED_BIT;
3714 #endif
3715 }
3716
3717 void
erts_check_off_heap(Process * p)3718 erts_check_off_heap(Process *p)
3719 {
3720 erts_check_off_heap2(p, NULL);
3721 }
3722
3723 #endif
3724