diff options
Diffstat (limited to 'erts/emulator/beam/erl_gc.c')
-rw-r--r-- | erts/emulator/beam/erl_gc.c | 2690 |
1 files changed, 2690 insertions, 0 deletions
diff --git a/erts/emulator/beam/erl_gc.c b/erts/emulator/beam/erl_gc.c new file mode 100644 index 0000000000..6945317e65 --- /dev/null +++ b/erts/emulator/beam/erl_gc.c @@ -0,0 +1,2690 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2002-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include "erl_vm.h" +#include "global.h" +#include "erl_process.h" +#include "erl_db.h" +#include "beam_catches.h" +#include "erl_binary.h" +#include "erl_bits.h" +#include "error.h" +#include "big.h" +#include "erl_gc.h" +#if HIPE +#include "hipe_stack.h" +#endif + +#define ERTS_INACT_WR_PB_LEAVE_MUCH_LIMIT 1 +#define ERTS_INACT_WR_PB_LEAVE_MUCH_PERCENTAGE 20 +#define ERTS_INACT_WR_PB_LEAVE_LIMIT 10 +#define ERTS_INACT_WR_PB_LEAVE_PERCENTAGE 10 + +/* + * Returns number of elements in an array. + */ +#define ALENGTH(a) (sizeof(a)/sizeof(a[0])) + +static erts_smp_spinlock_t info_lck; +static Uint garbage_cols; /* no of garbage collections */ +static Uint reclaimed; /* no of words reclaimed in GCs */ + +# define STACK_SZ_ON_HEAP(p) ((p)->hend - (p)->stop) +# define OverRunCheck(P) \ + if ((P)->stop < (P)->htop) { \ + erts_fprintf(stderr, "hend=%p\n", (p)->hend); \ + erts_fprintf(stderr, "stop=%p\n", (p)->stop); \ + erts_fprintf(stderr, "htop=%p\n", (p)->htop); \ + erts_fprintf(stderr, "heap=%p\n", (p)->heap); \ + erl_exit(ERTS_ABORT_EXIT, "%s, line %d: %T: Overrun stack and heap\n", \ + __FILE__,__LINE__,(P)->id); \ + } + +#ifdef DEBUG +#define ErtsGcQuickSanityCheck(P) \ +do { \ + ASSERT((P)->heap < (P)->hend); \ + ASSERT((P)->heap_sz == (P)->hend - (P)->heap); \ + ASSERT((P)->heap <= (P)->htop && (P)->htop <= (P)->hend); \ + ASSERT((P)->heap <= (P)->stop && (P)->stop <= (P)->hend); \ + ASSERT((P)->heap <= (P)->high_water && (P)->high_water <= (P)->hend);\ + OverRunCheck((P)); \ +} while (0) +#else +#define ErtsGcQuickSanityCheck(P) \ +do { \ + OverRunCheck((P)); \ +} while (0) +#endif +/* + * This structure describes the rootset for the GC. + */ +typedef struct roots { + Eterm* v; /* Pointers to vectors with terms to GC + * (e.g. the stack). + */ + Uint sz; /* Size of each vector. */ +} Roots; + +typedef struct { + Roots def[32]; /* Default storage. */ + Roots* roots; /* Pointer to root set array. */ + Uint size; /* Storage size. */ + int num_roots; /* Number of root arrays. */ +} Rootset; + +static Uint setup_rootset(Process*, Eterm*, int, Rootset*); +static void cleanup_rootset(Rootset *rootset); +static Uint combined_message_size(Process* p); +static void remove_message_buffers(Process* p); +static int major_collection(Process* p, int need, Eterm* objv, int nobj, Uint *recl); +static int minor_collection(Process* p, int need, Eterm* objv, int nobj, Uint *recl); +static void do_minor(Process *p, int new_sz, Eterm* objv, int nobj); +static Eterm* sweep_rootset(Rootset *rootset, Eterm* htop, char* src, Uint src_size); +static Eterm* sweep_one_area(Eterm* n_hp, Eterm* n_htop, char* src, Uint src_size); +static Eterm* sweep_one_heap(Eterm* heap_ptr, Eterm* heap_end, Eterm* htop, + char* src, Uint src_size); +static Eterm* collect_heap_frags(Process* p, Eterm* heap, + Eterm* htop, Eterm* objv, int nobj); +static Uint adjust_after_fullsweep(Process *p, int size_before, + int need, Eterm *objv, int nobj); +static void shrink_new_heap(Process *p, Uint new_sz, Eterm *objv, int nobj); +static void grow_new_heap(Process *p, Uint new_sz, Eterm* objv, int nobj); +static void sweep_proc_bins(Process *p, int fullsweep); +static void sweep_proc_funs(Process *p, int fullsweep); +static void sweep_proc_externals(Process *p, int fullsweep); +static void offset_heap(Eterm* hp, Uint sz, Sint offs, char* area, Uint area_size); +static void offset_heap_ptr(Eterm* hp, Uint sz, Sint offs, char* area, Uint area_size); +static void offset_rootset(Process *p, Sint offs, char* area, Uint area_size, + Eterm* objv, int nobj); +static void offset_off_heap(Process* p, Sint offs, char* area, Uint area_size); +static void offset_mqueue(Process *p, Sint offs, char* area, Uint area_size); + +#ifdef HARDDEBUG +static void disallow_heap_frag_ref_in_heap(Process* p); +static void disallow_heap_frag_ref_in_old_heap(Process* p); +static void disallow_heap_frag_ref(Process* p, Eterm* n_htop, Eterm* objv, int nobj); +#endif + +#ifdef ARCH_64 +# define MAX_HEAP_SIZES 154 +#else +# define MAX_HEAP_SIZES 55 +#endif + +static Sint heap_sizes[MAX_HEAP_SIZES]; /* Suitable heap sizes. */ +static int num_heap_sizes; /* Number of heap sizes. */ + +Uint erts_test_long_gc_sleep; /* Only used for testing... */ + +/* + * Initialize GC global data. + */ +void +erts_init_gc(void) +{ + int i = 0; + + erts_smp_spinlock_init(&info_lck, "gc_info"); + garbage_cols = 0; + reclaimed = 0; + erts_test_long_gc_sleep = 0; + + /* + * Heap sizes start growing in a Fibonacci sequence. + * + * Fib growth is not really ok for really large heaps, for + * example is fib(35) == 14meg, whereas fib(36) == 24meg; + * we really don't want that growth when the heaps are that big. + */ + + heap_sizes[0] = 34; + heap_sizes[1] = 55; + for (i = 2; i < 23; i++) { + heap_sizes[i] = heap_sizes[i-1] + heap_sizes[i-2]; + } + + /* At 1.3 mega words heap, we start to slow down. */ + for (i = 23; i < ALENGTH(heap_sizes); i++) { + heap_sizes[i] = 5*(heap_sizes[i-1]/4); + if (heap_sizes[i] < 0) { + /* Size turned negative. Discard this last size. */ + i--; + break; + } + } + num_heap_sizes = i; +} + +/* + * Find the next heap size equal to or greater than the given size (if offset == 0). + * + * If offset is 1, the next higher heap size is returned (always greater than size). + */ +Uint +erts_next_heap_size(Uint size, Uint offset) +{ + if (size < heap_sizes[0]) { + return heap_sizes[0]; + } else { + Sint* low = heap_sizes; + Sint* high = heap_sizes + num_heap_sizes; + Sint* mid; + + while (low < high) { + mid = low + (high-low) / 2; + if (size < mid[0]) { + high = mid; + } else if (size == mid[0]) { + ASSERT(mid+offset-heap_sizes < num_heap_sizes); + return mid[offset]; + } else if (size < mid[1]) { + ASSERT(mid[0] < size && size <= mid[1]); + ASSERT(mid+offset-heap_sizes < num_heap_sizes); + return mid[offset+1]; + } else { + low = mid + 1; + } + } + erl_exit(1, "no next heap size found: %d, offset %d\n", size, offset); + } + return 0; +} +/* + * Return the next heap size to use. Make sure we never return + * a smaller heap size than the minimum heap size for the process. + * (Use of the erlang:hibernate/3 BIF could have shrinked the + * heap below the minimum heap size.) + */ +static Uint +next_heap_size(Process* p, Uint size, Uint offset) +{ + size = erts_next_heap_size(size, offset); + return size < p->min_heap_size ? p->min_heap_size : size; +} + +Eterm +erts_heap_sizes(Process* p) +{ + int i; + int n = 0; + int big = 0; + Eterm res = NIL; + Eterm* hp; + Eterm* bigp; + + for (i = num_heap_sizes-1; i >= 0; i--) { + n += 2; + if (!MY_IS_SSMALL(heap_sizes[i])) { + big += BIG_UINT_HEAP_SIZE; + } + } + + /* + * We store all big numbers first on the heap, followed + * by all the cons cells. + */ + bigp = HAlloc(p, n+big); + hp = bigp+big; + for (i = num_heap_sizes-1; i >= 0; i--) { + Eterm num; + Sint sz = heap_sizes[i]; + + if (MY_IS_SSMALL(sz)) { + num = make_small(sz); + } else { + num = uint_to_big(sz, bigp); + bigp += BIG_UINT_HEAP_SIZE; + } + res = CONS(hp, num, res); + hp += 2; + } + return res; +} + +void +erts_gc_info(ErtsGCInfo *gcip) +{ + if (gcip) { + erts_smp_spin_lock(&info_lck); + gcip->garbage_collections = garbage_cols; + gcip->reclaimed = reclaimed; + erts_smp_spin_unlock(&info_lck); + } +} + +void +erts_offset_heap(Eterm* hp, Uint sz, Sint offs, Eterm* low, Eterm* high) +{ + offset_heap(hp, sz, offs, (char*) low, ((char *)high)-((char *)low)); +} + +void +erts_offset_heap_ptr(Eterm* hp, Uint sz, Sint offs, + Eterm* low, Eterm* high) +{ + offset_heap_ptr(hp, sz, offs, (char *) low, ((char *)high)-((char *)low)); +} + +#define ptr_within(ptr, low, high) ((ptr) < (high) && (ptr) >= (low)) + +void +erts_offset_off_heap(ErlOffHeap *ohp, Sint offs, Eterm* low, Eterm* high) +{ + if (ohp->mso && ptr_within((Eterm *)ohp->mso, low, high)) { + Eterm** uptr = (Eterm**) (void *) &ohp->mso; + *uptr += offs; + } + +#ifndef HYBRID /* FIND ME! */ + if (ohp->funs && ptr_within((Eterm *)ohp->funs, low, high)) { + Eterm** uptr = (Eterm**) (void *) &ohp->funs; + *uptr += offs; + } +#endif + + if (ohp->externals && ptr_within((Eterm *)ohp->externals, low, high)) { + Eterm** uptr = (Eterm**) (void *) &ohp->externals; + *uptr += offs; + } +} +#undef ptr_within + +Eterm +erts_gc_after_bif_call(Process* p, Eterm result, Eterm* regs, Uint arity) +{ + int cost; + + if (is_non_value(result)) { + if (p->freason == TRAP) { + cost = erts_garbage_collect(p, 0, p->def_arg_reg, p->arity); + } else { + cost = erts_garbage_collect(p, 0, regs, arity); + } + } else { + Eterm val[1]; + + val[0] = result; + cost = erts_garbage_collect(p, 0, val, 1); + result = val[0]; + } + BUMP_REDS(p, cost); + return result; +} + +/* + * Garbage collect a process. + * + * p: Pointer to the process structure. + * need: Number of Eterm words needed on the heap. + * objv: Array of terms to add to rootset; that is to preserve. + * nobj: Number of objects in objv. + */ +int +erts_garbage_collect(Process* p, int need, Eterm* objv, int nobj) +{ + Uint reclaimed_now = 0; + int done = 0; + Uint ms1, s1, us1; + + if (IS_TRACED_FL(p, F_TRACE_GC)) { + trace_gc(p, am_gc_start); + } + + erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS); + p->gcstatus = p->status; + p->status = P_GARBING; + if (erts_system_monitor_long_gc != 0) { + get_now(&ms1, &s1, &us1); + } + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS); + + erts_smp_locked_activity_begin(ERTS_ACTIVITY_GC); + + ERTS_CHK_OFFHEAP(p); + + ErtsGcQuickSanityCheck(p); + if (GEN_GCS(p) >= MAX_GEN_GCS(p)) { + FLAGS(p) |= F_NEED_FULLSWEEP; + } + + /* + * Test which type of GC to do. + */ + while (!done) { + if ((FLAGS(p) & F_NEED_FULLSWEEP) != 0) { + done = major_collection(p, need, objv, nobj, &reclaimed_now); + } else { + done = minor_collection(p, need, objv, nobj, &reclaimed_now); + } + } + + /* + * Finish. + */ + + ERTS_CHK_OFFHEAP(p); + + ErtsGcQuickSanityCheck(p); + + erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS); + p->status = p->gcstatus; + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS); + if (IS_TRACED_FL(p, F_TRACE_GC)) { + trace_gc(p, am_gc_end); + } + + erts_smp_locked_activity_end(ERTS_ACTIVITY_GC); + + if (erts_system_monitor_long_gc != 0) { + Uint ms2, s2, us2; + Sint t; + if (erts_test_long_gc_sleep) + while (0 != erts_milli_sleep(erts_test_long_gc_sleep)); + get_now(&ms2, &s2, &us2); + t = ms2 - ms1; + t = t*1000000 + s2 - s1; + t = t*1000 + ((Sint) (us2 - us1))/1000; + if (t > 0 && (Uint)t > erts_system_monitor_long_gc) { + monitor_long_gc(p, t); + } + } + if (erts_system_monitor_large_heap != 0) { + Uint size = HEAP_SIZE(p); + size += OLD_HEAP(p) ? OLD_HEND(p) - OLD_HEAP(p) : 0; + if (size >= erts_system_monitor_large_heap) + monitor_large_heap(p); + } + + erts_smp_spin_lock(&info_lck); + garbage_cols++; + reclaimed += reclaimed_now; + erts_smp_spin_unlock(&info_lck); + + FLAGS(p) &= ~F_FORCE_GC; + +#ifdef CHECK_FOR_HOLES + /* + * We intentionally do not rescan the areas copied by the GC. + * We trust the GC not to leave any holes. + */ + p->last_htop = p->htop; + p->last_mbuf = 0; +#endif + +#ifdef DEBUG + /* + * The scanning for pointers from the old_heap into the new_heap or + * heap fragments turned out to be costly, so we remember how far we + * have scanned this time and will start scanning there next time. + * (We will not detect wild writes into the old heap, or modifications + * of the old heap in-between garbage collections.) + */ + p->last_old_htop = p->old_htop; +#endif + + return ((int) (HEAP_TOP(p) - HEAP_START(p)) / 10); +} + +/* + * Place all living data on a the new heap; deallocate any old heap. + * Meant to be used by hibernate/3. + */ +void +erts_garbage_collect_hibernate(Process* p) +{ + Uint heap_size; + Eterm* heap; + Eterm* htop; + Rootset rootset; + int n; + char* src; + Uint src_size; + Uint actual_size; + char* area; + Uint area_size; + Sint offs; + + /* + * Preliminaries. + */ + erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS); + p->gcstatus = p->status; + p->status = P_GARBING; + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS); + erts_smp_locked_activity_begin(ERTS_ACTIVITY_GC); + ErtsGcQuickSanityCheck(p); + ASSERT(p->mbuf_sz == 0); + ASSERT(p->mbuf == 0); + ASSERT(p->stop == p->hend); /* Stack must be empty. */ + + /* + * Do it. + */ + + + heap_size = p->heap_sz + (p->old_htop - p->old_heap); + heap = (Eterm*) ERTS_HEAP_ALLOC(ERTS_ALC_T_TMP_HEAP, + sizeof(Eterm)*heap_size); + htop = heap; + + n = setup_rootset(p, p->arg_reg, p->arity, &rootset); + + src = (char *) p->heap; + src_size = (char *) p->htop - src; + htop = sweep_rootset(&rootset, htop, src, src_size); + htop = sweep_one_area(heap, htop, src, src_size); + + if (p->old_heap) { + src = (char *) p->old_heap; + src_size = (char *) p->old_htop - src; + htop = sweep_rootset(&rootset, htop, src, src_size); + htop = sweep_one_area(heap, htop, src, src_size); + } + + cleanup_rootset(&rootset); + + if (MSO(p).mso) { + sweep_proc_bins(p, 1); + } + if (MSO(p).funs) { + sweep_proc_funs(p, 1); + } + if (MSO(p).externals) { + sweep_proc_externals(p, 1); + } + + /* + * Update all pointers. + */ + ERTS_HEAP_FREE(ERTS_ALC_T_HEAP, + (void*)HEAP_START(p), + HEAP_SIZE(p) * sizeof(Eterm)); + if (p->old_heap) { + ERTS_HEAP_FREE(ERTS_ALC_T_OLD_HEAP, + (void*)p->old_heap, + (p->old_hend - p->old_heap) * sizeof(Eterm)); + p->old_heap = p->old_htop = p->old_hend = 0; + } + + p->heap = heap; + p->high_water = htop; + p->htop = htop; + p->hend = p->heap + heap_size; + p->stop = p->hend; + p->heap_sz = heap_size; + + heap_size = actual_size = p->htop - p->heap; + if (heap_size == 0) { + heap_size = 1; /* We want a heap... */ + } + + FLAGS(p) &= ~F_FORCE_GC; + + /* + * Move the heap to its final destination. + * + * IMPORTANT: We have garbage collected to a temporary heap and + * then copy the result to a newly allocated heap of exact size. + * This is intentional and important! Garbage collecting as usual + * and then shrinking the heap by reallocating it caused serious + * fragmentation problems when large amounts of processes were + * hibernated. + */ + + ASSERT(p->hend - p->stop == 0); /* Empty stack */ + ASSERT(actual_size < p->heap_sz); + + heap = ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP, sizeof(Eterm)*heap_size); + sys_memcpy((void *) heap, (void *) p->heap, actual_size*sizeof(Eterm)); + ERTS_HEAP_FREE(ERTS_ALC_T_TMP_HEAP, p->heap, p->heap_sz*sizeof(Eterm)); + + p->stop = p->hend = heap + heap_size; + + offs = heap - p->heap; + area = (char *) p->heap; + area_size = ((char *) p->htop) - area; + offset_heap(heap, actual_size, offs, area, area_size); + p->high_water = heap + (p->high_water - p->heap); + offset_rootset(p, offs, area, area_size, p->arg_reg, p->arity); + p->htop = heap + actual_size; + p->heap = heap; + p->heap_sz = heap_size; + + +#ifdef CHECK_FOR_HOLES + p->last_htop = p->htop; + p->last_mbuf = 0; +#endif +#ifdef DEBUG + p->last_old_htop = NULL; +#endif + + /* + * Finishing. + */ + + ErtsGcQuickSanityCheck(p); + + erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS); + p->status = p->gcstatus; + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS); + erts_smp_locked_activity_end(ERTS_ACTIVITY_GC); +} + + +void +erts_garbage_collect_literals(Process* p, Eterm* literals, Uint lit_size) +{ + Uint byte_lit_size = sizeof(Eterm)*lit_size; + Uint old_heap_size; + Eterm* temp_lit; + Sint offs; + Rootset rootset; /* Rootset for GC (stack, dictionary, etc). */ + Roots* roots; + char* area; + Uint area_size; + Eterm* old_htop; + int n; + + /* + * Set GC state. + */ + erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS); + p->gcstatus = p->status; + p->status = P_GARBING; + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS); + erts_smp_locked_activity_begin(ERTS_ACTIVITY_GC); + + /* + * We assume that the caller has already done a major collection + * (which has discarded the old heap), so that we don't have to cope + * with pointer to literals on the old heap. We will now allocate + * an old heap to contain the literals. + */ + + ASSERT(p->old_heap == 0); /* Must NOT have an old heap yet. */ + old_heap_size = erts_next_heap_size(lit_size, 0); + p->old_heap = p->old_htop = (Eterm*) ERTS_HEAP_ALLOC(ERTS_ALC_T_OLD_HEAP, + sizeof(Eterm)*old_heap_size); + p->old_hend = p->old_heap + old_heap_size; + + /* + * We soon want to garbage collect the literals. But since a GC is + * destructive (MOVED markers are written), we must copy the literals + * to a temporary area and change all references to literals. + */ + temp_lit = (Eterm *) erts_alloc(ERTS_ALC_T_TMP, byte_lit_size); + sys_memcpy(temp_lit, literals, byte_lit_size); + offs = temp_lit - literals; + offset_heap(temp_lit, lit_size, offs, (char *) literals, byte_lit_size); + offset_heap(p->heap, p->htop - p->heap, offs, (char *) literals, byte_lit_size); + offset_rootset(p, offs, (char *) literals, byte_lit_size, p->arg_reg, p->arity); + + /* + * Now the literals are placed in memory that is safe to write into, + * so now we GC the literals into the old heap. First we go through the + * rootset. + */ + + area = (char *) temp_lit; + area_size = byte_lit_size; + n = setup_rootset(p, p->arg_reg, p->arity, &rootset); + roots = rootset.roots; + old_htop = p->old_htop; + while (n--) { + Eterm* g_ptr = roots->v; + Uint g_sz = roots->sz; + Eterm* ptr; + Eterm val; + + roots++; + + while (g_sz--) { + Eterm gval = *g_ptr; + + switch (primary_tag(gval)) { + case TAG_PRIMARY_BOXED: + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + *g_ptr++ = val; + } else if (in_area(ptr, area, area_size)) { + MOVE_BOXED(ptr,val,old_htop,g_ptr++); + } else { + g_ptr++; + } + break; + case TAG_PRIMARY_LIST: + ptr = list_val(gval); + val = *ptr; + if (is_non_value(val)) { /* Moved */ + *g_ptr++ = ptr[1]; + } else if (in_area(ptr, area, area_size)) { + MOVE_CONS(ptr,val,old_htop,g_ptr++); + } else { + g_ptr++; + } + break; + default: + g_ptr++; + break; + } + } + } + ASSERT(p->old_htop <= old_htop && old_htop <= p->old_hend); + cleanup_rootset(&rootset); + + /* + * Now all references in the rootset to the literals have been updated. + * Now we'll have to go through all heaps updating all other references. + */ + + old_htop = sweep_one_heap(p->heap, p->htop, old_htop, area, area_size); + old_htop = sweep_one_area(p->old_heap, old_htop, area, area_size); + ASSERT(p->old_htop <= old_htop && old_htop <= p->old_hend); + p->old_htop = old_htop; + + /* + * We no longer need this temporary area. + */ + erts_free(ERTS_ALC_T_TMP, (void *) temp_lit); + + /* + * Restore status. + */ + erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS); + p->status = p->gcstatus; + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS); + erts_smp_locked_activity_end(ERTS_ACTIVITY_GC); +} + +static int +minor_collection(Process* p, int need, Eterm* objv, int nobj, Uint *recl) +{ + Uint mature = HIGH_WATER(p) - HEAP_START(p); + + /* + * Allocate an old heap if we don't have one and if we'll need one. + */ + + if (OLD_HEAP(p) == NULL && mature != 0) { + Eterm* n_old; + + /* Note: We choose a larger heap size than strictly needed, + * which seems to reduce the number of fullsweeps. + * This improved Estone by more than 1200 estones on my computer + * (Ultra Sparc 10). + */ + size_t new_sz = erts_next_heap_size(HEAP_TOP(p) - HEAP_START(p), 1); + + /* Create new, empty old_heap */ + n_old = (Eterm *) ERTS_HEAP_ALLOC(ERTS_ALC_T_OLD_HEAP, + sizeof(Eterm)*new_sz); + + OLD_HEND(p) = n_old + new_sz; + OLD_HEAP(p) = OLD_HTOP(p) = n_old; + } + + /* + * Do a minor collection if there is an old heap and if it + * is large enough. + */ + + if (OLD_HEAP(p) && mature <= OLD_HEND(p) - OLD_HTOP(p)) { + ErlMessage *msgp; + Uint size_after; + Uint need_after; + Uint stack_size = STACK_SZ_ON_HEAP(p); + Uint fragments = MBUF_SIZE(p) + combined_message_size(p); + Uint size_before = fragments + (HEAP_TOP(p) - HEAP_START(p)); + Uint new_sz = next_heap_size(p, HEAP_SIZE(p) + fragments, 0); + + do_minor(p, new_sz, objv, nobj); + + /* + * Copy newly received message onto the end of the new heap. + */ + ErtsGcQuickSanityCheck(p); + for (msgp = p->msg.first; msgp; msgp = msgp->next) { + if (msgp->data.attached) { + erts_move_msg_attached_data_to_heap(&p->htop, &p->off_heap, msgp); + ErtsGcQuickSanityCheck(p); + } + } + ErtsGcQuickSanityCheck(p); + + GEN_GCS(p)++; + size_after = HEAP_TOP(p) - HEAP_START(p); + need_after = size_after + need + stack_size; + *recl += (size_before - size_after); + + /* + * Excessively large heaps should be shrunk, but + * don't even bother on reasonable small heaps. + * + * The reason for this is that after tenuring, we often + * use a really small portion of new heap, therefore, unless + * the heap size is substantial, we don't want to shrink. + */ + + if ((HEAP_SIZE(p) > 3000) && (4 * need_after < HEAP_SIZE(p)) && + ((HEAP_SIZE(p) > 8000) || + (HEAP_SIZE(p) > (OLD_HEND(p) - OLD_HEAP(p))))) { + Uint wanted = 3 * need_after; + Uint old_heap_sz = OLD_HEND(p) - OLD_HEAP(p); + + /* + * Additional test to make sure we don't make the heap too small + * compared to the size of the older generation heap. + */ + if (wanted*9 < old_heap_sz) { + Uint new_wanted = old_heap_sz / 8; + if (new_wanted > wanted) { + wanted = new_wanted; + } + } + + if (wanted < MIN_HEAP_SIZE(p)) { + wanted = MIN_HEAP_SIZE(p); + } else { + wanted = next_heap_size(p, wanted, 0); + } + if (wanted < HEAP_SIZE(p)) { + shrink_new_heap(p, wanted, objv, nobj); + } + ASSERT(HEAP_SIZE(p) == next_heap_size(p, HEAP_SIZE(p), 0)); + return 1; /* We are done. */ + } + + if (HEAP_SIZE(p) >= need_after) { + /* + * The heap size turned out to be just right. We are done. + */ + ASSERT(HEAP_SIZE(p) == next_heap_size(p, HEAP_SIZE(p), 0)); + return 1; + } + } + + /* + * Still not enough room after minor collection. Must force a major collection. + */ + FLAGS(p) |= F_NEED_FULLSWEEP; + return 0; +} + +/* + * HiPE native code stack scanning procedures: + * - fullsweep_nstack() + * - gensweep_nstack() + * - offset_nstack() + */ +#if defined(HIPE) + +#define GENSWEEP_NSTACK(p,old_htop,n_htop) \ + do { \ + Eterm *tmp_old_htop = old_htop; \ + Eterm *tmp_n_htop = n_htop; \ + gensweep_nstack((p), &tmp_old_htop, &tmp_n_htop); \ + old_htop = tmp_old_htop; \ + n_htop = tmp_n_htop; \ + } while(0) + +/* + * offset_nstack() can ignore the descriptor-based traversal the other + * nstack procedures use and simply call offset_heap_ptr() instead. + * This relies on two facts: + * 1. The only live non-Erlang terms on an nstack are return addresses, + * and they will be skipped thanks to the low/high range check. + * 2. Dead values, even if mistaken for pointers into the low/high area, + * can be offset safely since they won't be dereferenced. + * + * XXX: WARNING: If HiPE starts storing other non-Erlang values on the + * nstack, such as floats, then this will have to be changed. + */ +#define offset_nstack(p,offs,area,area_size) offset_heap_ptr(hipe_nstack_start((p)),hipe_nstack_used((p)),(offs),(area),(area_size)) + +#else /* !HIPE */ + +#define fullsweep_nstack(p,n_htop) (n_htop) +#define GENSWEEP_NSTACK(p,old_htop,n_htop) do{}while(0) +#define offset_nstack(p,offs,area,area_size) do{}while(0) + +#endif /* HIPE */ + +static void +do_minor(Process *p, int new_sz, Eterm* objv, int nobj) +{ + Rootset rootset; /* Rootset for GC (stack, dictionary, etc). */ + Roots* roots; + Eterm* n_htop; + int n; + Eterm* ptr; + Eterm val; + Eterm gval; + char* heap = (char *) HEAP_START(p); + Uint heap_size = (char *) HEAP_TOP(p) - heap; + Uint mature_size = (char *) HIGH_WATER(p) - heap; + Eterm* old_htop = OLD_HTOP(p); + Eterm* n_heap; + + n_htop = n_heap = (Eterm*) ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP, + sizeof(Eterm)*new_sz); + + if (MBUF(p) != NULL) { + n_htop = collect_heap_frags(p, n_heap, n_htop, objv, nobj); + } + + n = setup_rootset(p, objv, nobj, &rootset); + roots = rootset.roots; + + GENSWEEP_NSTACK(p, old_htop, n_htop); + while (n--) { + Eterm* g_ptr = roots->v; + Uint g_sz = roots->sz; + + roots++; + while (g_sz--) { + gval = *g_ptr; + + switch (primary_tag(gval)) { + + case TAG_PRIMARY_BOXED: { + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + *g_ptr++ = val; + } else if (in_area(ptr, heap, mature_size)) { + MOVE_BOXED(ptr,val,old_htop,g_ptr++); + } else if (in_area(ptr, heap, heap_size)) { + MOVE_BOXED(ptr,val,n_htop,g_ptr++); + } else { + g_ptr++; + } + break; + } + + case TAG_PRIMARY_LIST: { + ptr = list_val(gval); + val = *ptr; + if (is_non_value(val)) { /* Moved */ + *g_ptr++ = ptr[1]; + } else if (in_area(ptr, heap, mature_size)) { + MOVE_CONS(ptr,val,old_htop,g_ptr++); + } else if (in_area(ptr, heap, heap_size)) { + MOVE_CONS(ptr,val,n_htop,g_ptr++); + } else { + g_ptr++; + } + break; + } + + default: + g_ptr++; + break; + } + } + } + + cleanup_rootset(&rootset); + + /* + * Now all references in the rootset point to the new heap. However, + * most references on the new heap point to the old heap so the next stage + * is to scan through the new heap evacuating data from the old heap + * until all is changed. + */ + + if (mature_size == 0) { + n_htop = sweep_one_area(n_heap, n_htop, heap, heap_size); + } else { + Eterm* n_hp = n_heap; + + while (n_hp != n_htop) { + Eterm* ptr; + Eterm val; + Eterm gval = *n_hp; + + switch (primary_tag(gval)) { + case TAG_PRIMARY_BOXED: { + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + *n_hp++ = val; + } else if (in_area(ptr, heap, mature_size)) { + MOVE_BOXED(ptr,val,old_htop,n_hp++); + } else if (in_area(ptr, heap, heap_size)) { + MOVE_BOXED(ptr,val,n_htop,n_hp++); + } else { + n_hp++; + } + break; + } + case TAG_PRIMARY_LIST: { + ptr = list_val(gval); + val = *ptr; + if (is_non_value(val)) { + *n_hp++ = ptr[1]; + } else if (in_area(ptr, heap, mature_size)) { + MOVE_CONS(ptr,val,old_htop,n_hp++); + } else if (in_area(ptr, heap, heap_size)) { + MOVE_CONS(ptr,val,n_htop,n_hp++); + } else { + n_hp++; + } + break; + } + case TAG_PRIMARY_HEADER: { + if (!header_is_thing(gval)) + n_hp++; + else { + if (header_is_bin_matchstate(gval)) { + ErlBinMatchState *ms = (ErlBinMatchState*) n_hp; + ErlBinMatchBuffer *mb = &(ms->mb); + Eterm* origptr = &(mb->orig); + ptr = boxed_val(*origptr); + val = *ptr; + if (IS_MOVED(val)) { + *origptr = val; + mb->base = binary_bytes(val); + } else if (in_area(ptr, heap, mature_size)) { + MOVE_BOXED(ptr,val,old_htop,origptr); + mb->base = binary_bytes(mb->orig); + } else if (in_area(ptr, heap, heap_size)) { + MOVE_BOXED(ptr,val,n_htop,origptr); + mb->base = binary_bytes(mb->orig); + } + } + n_hp += (thing_arityval(gval)+1); + } + break; + } + default: + n_hp++; + break; + } + } + } + + /* + * And also if we have been tenuring, references on the second generation + * may point to the old (soon to be deleted) new_heap. + */ + + if (OLD_HTOP(p) < old_htop) { + old_htop = sweep_one_area(OLD_HTOP(p), old_htop, heap, heap_size); + } + OLD_HTOP(p) = old_htop; + HIGH_WATER(p) = (HEAP_START(p) != HIGH_WATER(p)) ? n_heap : n_htop; + + if (MSO(p).mso) { + sweep_proc_bins(p, 0); + } + + if (MSO(p).funs) { + sweep_proc_funs(p, 0); + } + if (MSO(p).externals) { + sweep_proc_externals(p, 0); + } + +#ifdef HARDDEBUG + /* + * Go through the old_heap before, and try to find references from the old_heap + * into the old new_heap that has just been evacuated and is about to be freed + * (as well as looking for reference into heap fragments, of course). + */ + disallow_heap_frag_ref_in_old_heap(p); +#endif + + /* Copy stack to end of new heap */ + n = p->hend - p->stop; + sys_memcpy(n_heap + new_sz - n, p->stop, n * sizeof(Eterm)); + p->stop = n_heap + new_sz - n; + + ERTS_HEAP_FREE(ERTS_ALC_T_HEAP, + (void*)HEAP_START(p), + HEAP_SIZE(p) * sizeof(Eterm)); + HEAP_START(p) = n_heap; + HEAP_TOP(p) = n_htop; + HEAP_SIZE(p) = new_sz; + HEAP_END(p) = n_heap + new_sz; + +#ifdef HARDDEBUG + disallow_heap_frag_ref_in_heap(p); +#endif + remove_message_buffers(p); +} + +/* + * Major collection. DISCARD the old heap. + */ + +static int +major_collection(Process* p, int need, Eterm* objv, int nobj, Uint *recl) +{ + Rootset rootset; + Roots* roots; + int size_before; + Eterm* n_heap; + Eterm* n_htop; + char* src = (char *) HEAP_START(p); + Uint src_size = (char *) HEAP_TOP(p) - src; + char* oh = (char *) OLD_HEAP(p); + Uint oh_size = (char *) OLD_HTOP(p) - oh; + int n; + Uint new_sz; + Uint fragments = MBUF_SIZE(p) + combined_message_size(p); + ErlMessage *msgp; + + size_before = fragments + (HEAP_TOP(p) - HEAP_START(p)); + + /* + * Do a fullsweep GC. First figure out the size of the heap + * to receive all live data. + */ + + new_sz = HEAP_SIZE(p) + fragments + (OLD_HTOP(p) - OLD_HEAP(p)); + /* + * We used to do + * + * new_sz += STACK_SZ_ON_HEAP(p); + * + * here for no obvious reason. (The stack size is already counted once + * in HEAP_SIZE(p).) + */ + new_sz = next_heap_size(p, new_sz, 0); + + /* + * Should we grow although we don't actually need to? + */ + + if (new_sz == HEAP_SIZE(p) && FLAGS(p) & F_HEAP_GROW) { + new_sz = next_heap_size(p, HEAP_SIZE(p), 1); + } + FLAGS(p) &= ~(F_HEAP_GROW|F_NEED_FULLSWEEP); + n_htop = n_heap = (Eterm *) ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP, + sizeof(Eterm)*new_sz); + + /* + * Get rid of heap fragments. + */ + + if (MBUF(p) != NULL) { + n_htop = collect_heap_frags(p, n_heap, n_htop, objv, nobj); + } + + /* + * Copy all top-level terms directly referenced by the rootset to + * the new new_heap. + */ + + n = setup_rootset(p, objv, nobj, &rootset); + n_htop = fullsweep_nstack(p, n_htop); + roots = rootset.roots; + while (n--) { + Eterm* g_ptr = roots->v; + Eterm g_sz = roots->sz; + + roots++; + while (g_sz--) { + Eterm* ptr; + Eterm val; + Eterm gval = *g_ptr; + + switch (primary_tag(gval)) { + + case TAG_PRIMARY_BOXED: { + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + *g_ptr++ = val; + } else if (in_area(ptr, src, src_size) || in_area(ptr, oh, oh_size)) { + MOVE_BOXED(ptr,val,n_htop,g_ptr++); + } else { + g_ptr++; + } + continue; + } + + case TAG_PRIMARY_LIST: { + ptr = list_val(gval); + val = *ptr; + if (is_non_value(val)) { + *g_ptr++ = ptr[1]; + } else if (in_area(ptr, src, src_size) || in_area(ptr, oh, oh_size)) { + MOVE_CONS(ptr,val,n_htop,g_ptr++); + } else { + g_ptr++; + } + continue; + } + + default: { + g_ptr++; + continue; + } + } + } + } + + cleanup_rootset(&rootset); + + /* + * Now all references on the stack point to the new heap. However, + * most references on the new heap point to the old heap so the next stage + * is to scan through the new heap evacuating data from the old heap + * until all is copied. + */ + + if (oh_size == 0) { + n_htop = sweep_one_area(n_heap, n_htop, src, src_size); + } else { + Eterm* n_hp = n_heap; + + while (n_hp != n_htop) { + Eterm* ptr; + Eterm val; + Eterm gval = *n_hp; + + switch (primary_tag(gval)) { + case TAG_PRIMARY_BOXED: { + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + *n_hp++ = val; + } else if (in_area(ptr, src, src_size) || in_area(ptr, oh, oh_size)) { + MOVE_BOXED(ptr,val,n_htop,n_hp++); + } else { + n_hp++; + } + break; + } + case TAG_PRIMARY_LIST: { + ptr = list_val(gval); + val = *ptr; + if (is_non_value(val)) { + *n_hp++ = ptr[1]; + } else if (in_area(ptr, src, src_size) || in_area(ptr, oh, oh_size)) { + MOVE_CONS(ptr,val,n_htop,n_hp++); + } else { + n_hp++; + } + break; + } + case TAG_PRIMARY_HEADER: { + if (!header_is_thing(gval)) + n_hp++; + else { + if (header_is_bin_matchstate(gval)) { + ErlBinMatchState *ms = (ErlBinMatchState*) n_hp; + ErlBinMatchBuffer *mb = &(ms->mb); + Eterm* origptr; + origptr = &(mb->orig); + ptr = boxed_val(*origptr); + val = *ptr; + if (IS_MOVED(val)) { + *origptr = val; + mb->base = binary_bytes(*origptr); + } else if (in_area(ptr, src, src_size) || + in_area(ptr, oh, oh_size)) { + MOVE_BOXED(ptr,val,n_htop,origptr); + mb->base = binary_bytes(*origptr); + ptr = boxed_val(*origptr); + val = *ptr; + } + } + n_hp += (thing_arityval(gval)+1); + } + break; + } + default: + n_hp++; + break; + } + } + } + + if (MSO(p).mso) { + sweep_proc_bins(p, 1); + } + if (MSO(p).funs) { + sweep_proc_funs(p, 1); + } + if (MSO(p).externals) { + sweep_proc_externals(p, 1); + } + + if (OLD_HEAP(p) != NULL) { + ERTS_HEAP_FREE(ERTS_ALC_T_OLD_HEAP, + OLD_HEAP(p), + (OLD_HEND(p) - OLD_HEAP(p)) * sizeof(Eterm)); + OLD_HEAP(p) = OLD_HTOP(p) = OLD_HEND(p) = NULL; + } + + /* Move the stack to the end of the heap */ + n = HEAP_END(p) - p->stop; + sys_memcpy(n_heap + new_sz - n, p->stop, n * sizeof(Eterm)); + p->stop = n_heap + new_sz - n; + + ERTS_HEAP_FREE(ERTS_ALC_T_HEAP, + (void *) HEAP_START(p), + (HEAP_END(p) - HEAP_START(p)) * sizeof(Eterm)); + HEAP_START(p) = n_heap; + HEAP_TOP(p) = n_htop; + HEAP_SIZE(p) = new_sz; + HEAP_END(p) = n_heap + new_sz; + GEN_GCS(p) = 0; + + HIGH_WATER(p) = HEAP_TOP(p); + + ErtsGcQuickSanityCheck(p); + /* + * Copy newly received message onto the end of the new heap. + */ + for (msgp = p->msg.first; msgp; msgp = msgp->next) { + if (msgp->data.attached) { + erts_move_msg_attached_data_to_heap(&p->htop, &p->off_heap, msgp); + ErtsGcQuickSanityCheck(p); + } + } + + *recl += adjust_after_fullsweep(p, size_before, need, objv, nobj); + +#ifdef HARDDEBUG + disallow_heap_frag_ref_in_heap(p); +#endif + remove_message_buffers(p); + + ErtsGcQuickSanityCheck(p); + return 1; /* We are done. */ +} + +static Uint +adjust_after_fullsweep(Process *p, int size_before, int need, Eterm *objv, int nobj) +{ + int wanted, sz, size_after, need_after; + int stack_size = STACK_SZ_ON_HEAP(p); + Uint reclaimed_now; + + size_after = (HEAP_TOP(p) - HEAP_START(p)); + reclaimed_now = (size_before - size_after); + + /* + * Resize the heap if needed. + */ + + need_after = size_after + need + stack_size; + if (HEAP_SIZE(p) < need_after) { + /* Too small - grow to match requested need */ + sz = next_heap_size(p, need_after, 0); + grow_new_heap(p, sz, objv, nobj); + } else if (3 * HEAP_SIZE(p) < 4 * need_after){ + /* Need more than 75% of current, postpone to next GC.*/ + FLAGS(p) |= F_HEAP_GROW; + } else if (4 * need_after < HEAP_SIZE(p) && HEAP_SIZE(p) > H_MIN_SIZE){ + /* We need less than 25% of the current heap, shrink.*/ + /* XXX - This is how it was done in the old GC: + wanted = 4 * need_after; + I think this is better as fullsweep is used mainly on + small memory systems, but I could be wrong... */ + wanted = 2 * need_after; + if (wanted < p->min_heap_size) { + sz = p->min_heap_size; + } else { + sz = next_heap_size(p, wanted, 0); + } + if (sz < HEAP_SIZE(p)) { + shrink_new_heap(p, sz, objv, nobj); + } + } + + return reclaimed_now; +} + +/* + * Return the size of all message buffers that are NOT linked in the + * mbuf list. + */ +static Uint +combined_message_size(Process* p) +{ + Uint sz = 0; + ErlMessage *msgp; + + for (msgp = p->msg.first; msgp; msgp = msgp->next) { + if (msgp->data.attached) { + sz += erts_msg_attached_data_size(msgp); + } + } + return sz; +} + +/* + * Remove all message buffers. + */ +static void +remove_message_buffers(Process* p) +{ + ErlHeapFragment* bp = MBUF(p); + + MBUF(p) = NULL; + MBUF_SIZE(p) = 0; + while (bp != NULL) { + ErlHeapFragment* next_bp = bp->next; + free_message_buffer(bp); + bp = next_bp; + } +} + +/* + * Go through one root set array, move everything that it is one of the + * heap fragments to our new heap. + */ +static Eterm* +collect_root_array(Process* p, Eterm* n_htop, Eterm* objv, int nobj) +{ + ErlHeapFragment* qb; + Eterm gval; + Eterm* ptr; + Eterm val; + + ASSERT(p->htop != NULL); + while (nobj--) { + gval = *objv; + + switch (primary_tag(gval)) { + + case TAG_PRIMARY_BOXED: { + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + *objv++ = val; + } else { + for (qb = MBUF(p); qb != NULL; qb = qb->next) { + if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + MOVE_BOXED(ptr,val,n_htop,objv); + break; + } + } + objv++; + } + break; + } + + case TAG_PRIMARY_LIST: { + ptr = list_val(gval); + val = *ptr; + if (is_non_value(val)) { + *objv++ = ptr[1]; + } else { + for (qb = MBUF(p); qb != NULL; qb = qb->next) { + if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + MOVE_CONS(ptr,val,n_htop,objv); + break; + } + } + objv++; + } + break; + } + + default: { + objv++; + break; + } + } + } + return n_htop; +} + +#ifdef HARDDEBUG + +/* + * Routines to verify that we don't have pointer into heap fragments from + * that are not allowed to have them. + * + * For performance reasons, we use _unchecked_list_val(), _unchecked_boxed_val(), + * and so on to avoid a function call. + */ + +static void +disallow_heap_frag_ref(Process* p, Eterm* n_htop, Eterm* objv, int nobj) +{ + ErlHeapFragment* mbuf; + ErlHeapFragment* qb; + Eterm gval; + Eterm* ptr; + Eterm val; + + ASSERT(p->htop != NULL); + mbuf = MBUF(p); + + while (nobj--) { + gval = *objv; + + switch (primary_tag(gval)) { + + case TAG_PRIMARY_BOXED: { + ptr = _unchecked_boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + objv++; + } else { + for (qb = mbuf; qb != NULL; qb = qb->next) { + if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + abort(); + } + } + objv++; + } + break; + } + + case TAG_PRIMARY_LIST: { + ptr = _unchecked_list_val(gval); + val = *ptr; + if (is_non_value(val)) { + objv++; + } else { + for (qb = mbuf; qb != NULL; qb = qb->next) { + if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + abort(); + } + } + objv++; + } + break; + } + + default: { + objv++; + break; + } + } + } +} + +static void +disallow_heap_frag_ref_in_heap(Process* p) +{ + Eterm* hp; + Eterm* htop; + Eterm* heap; + Uint heap_size; + + if (p->mbuf == 0) { + return; + } + + htop = p->htop; + heap = p->heap; + heap_size = (htop - heap)*sizeof(Eterm); + + hp = heap; + while (hp < htop) { + ErlHeapFragment* qb; + Eterm* ptr; + Eterm val; + + val = *hp++; + switch (primary_tag(val)) { + case TAG_PRIMARY_BOXED: + ptr = _unchecked_boxed_val(val); + if (!in_area(ptr, heap, heap_size)) { + for (qb = MBUF(p); qb != NULL; qb = qb->next) { + if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + abort(); + } + } + } + break; + case TAG_PRIMARY_LIST: + ptr = _unchecked_list_val(val); + if (!in_area(ptr, heap, heap_size)) { + for (qb = MBUF(p); qb != NULL; qb = qb->next) { + if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + abort(); + } + } + } + break; + case TAG_PRIMARY_HEADER: + if (header_is_thing(val)) { + hp += _unchecked_thing_arityval(val); + } + break; + } + } +} + +static void +disallow_heap_frag_ref_in_old_heap(Process* p) +{ + Eterm* hp; + Eterm* htop; + Eterm* old_heap; + Uint old_heap_size; + Eterm* new_heap; + Uint new_heap_size; + + htop = p->old_htop; + old_heap = p->old_heap; + old_heap_size = (htop - old_heap)*sizeof(Eterm); + new_heap = p->heap; + new_heap_size = (p->htop - new_heap)*sizeof(Eterm); + + ASSERT(!p->last_old_htop + || (old_heap <= p->last_old_htop && p->last_old_htop <= htop)); + hp = p->last_old_htop ? p->last_old_htop : old_heap; + while (hp < htop) { + ErlHeapFragment* qb; + Eterm* ptr; + Eterm val; + + val = *hp++; + switch (primary_tag(val)) { + case TAG_PRIMARY_BOXED: + ptr = (Eterm *) val; + if (!in_area(ptr, old_heap, old_heap_size)) { + if (in_area(ptr, new_heap, new_heap_size)) { + abort(); + } + for (qb = MBUF(p); qb != NULL; qb = qb->next) { + if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + abort(); + } + } + } + break; + case TAG_PRIMARY_LIST: + ptr = (Eterm *) val; + if (!in_area(ptr, old_heap, old_heap_size)) { + if (in_area(ptr, new_heap, new_heap_size)) { + abort(); + } + for (qb = MBUF(p); qb != NULL; qb = qb->next) { + if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + abort(); + } + } + } + break; + case TAG_PRIMARY_HEADER: + if (header_is_thing(val)) { + hp += _unchecked_thing_arityval(val); + if (!in_area(hp, old_heap, old_heap_size+1)) { + abort(); + } + } + break; + } + } +} +#endif + +static Eterm* +sweep_rootset(Rootset* rootset, Eterm* htop, char* src, Uint src_size) +{ + Roots* roots = rootset->roots; + Uint n = rootset->num_roots; + Eterm* ptr; + Eterm gval; + Eterm val; + + while (n--) { + Eterm* g_ptr = roots->v; + Uint g_sz = roots->sz; + + roots++; + while (g_sz--) { + gval = *g_ptr; + + switch (primary_tag(gval)) { + case TAG_PRIMARY_BOXED: { + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + *g_ptr++ = val; + } else if (in_area(ptr, src, src_size)) { + MOVE_BOXED(ptr,val,htop,g_ptr++); + } else { + g_ptr++; + } + break; + } + case TAG_PRIMARY_LIST: { + ptr = list_val(gval); + val = *ptr; + if (is_non_value(val)) { /* Moved */ + *g_ptr++ = ptr[1]; + } else if (in_area(ptr, src, src_size)) { + MOVE_CONS(ptr,val,htop,g_ptr++); + } else { + g_ptr++; + } + break; + } + + default: + g_ptr++; + break; + } + } + } + return htop; +} + + +static Eterm* +sweep_one_area(Eterm* n_hp, Eterm* n_htop, char* src, Uint src_size) +{ + while (n_hp != n_htop) { + Eterm* ptr; + Eterm val; + Eterm gval = *n_hp; + + switch (primary_tag(gval)) { + case TAG_PRIMARY_BOXED: { + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + *n_hp++ = val; + } else if (in_area(ptr, src, src_size)) { + MOVE_BOXED(ptr,val,n_htop,n_hp++); + } else { + n_hp++; + } + break; + } + case TAG_PRIMARY_LIST: { + ptr = list_val(gval); + val = *ptr; + if (is_non_value(val)) { + *n_hp++ = ptr[1]; + } else if (in_area(ptr, src, src_size)) { + MOVE_CONS(ptr,val,n_htop,n_hp++); + } else { + n_hp++; + } + break; + } + case TAG_PRIMARY_HEADER: { + if (!header_is_thing(gval)) { + n_hp++; + } else { + if (header_is_bin_matchstate(gval)) { + ErlBinMatchState *ms = (ErlBinMatchState*) n_hp; + ErlBinMatchBuffer *mb = &(ms->mb); + Eterm* origptr; + origptr = &(mb->orig); + ptr = boxed_val(*origptr); + val = *ptr; + if (IS_MOVED(val)) { + *origptr = val; + mb->base = binary_bytes(*origptr); + } else if (in_area(ptr, src, src_size)) { + MOVE_BOXED(ptr,val,n_htop,origptr); + mb->base = binary_bytes(*origptr); + } + } + n_hp += (thing_arityval(gval)+1); + } + break; + } + default: + n_hp++; + break; + } + } + return n_htop; +} + +static Eterm* +sweep_one_heap(Eterm* heap_ptr, Eterm* heap_end, Eterm* htop, char* src, Uint src_size) +{ + while (heap_ptr < heap_end) { + Eterm* ptr; + Eterm val; + Eterm gval = *heap_ptr; + + switch (primary_tag(gval)) { + case TAG_PRIMARY_BOXED: { + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED(val)) { + ASSERT(is_boxed(val)); + *heap_ptr++ = val; + } else if (in_area(ptr, src, src_size)) { + MOVE_BOXED(ptr,val,htop,heap_ptr++); + } else { + heap_ptr++; + } + break; + } + case TAG_PRIMARY_LIST: { + ptr = list_val(gval); + val = *ptr; + if (is_non_value(val)) { + *heap_ptr++ = ptr[1]; + } else if (in_area(ptr, src, src_size)) { + MOVE_CONS(ptr,val,htop,heap_ptr++); + } else { + heap_ptr++; + } + break; + } + case TAG_PRIMARY_HEADER: { + if (!header_is_thing(gval)) { + heap_ptr++; + } else { + heap_ptr += (thing_arityval(gval)+1); + } + break; + } + default: + heap_ptr++; + break; + } + } + return htop; +} + +/* + * Collect heap fragments and check that they point in the correct direction. + */ + +static Eterm* +collect_heap_frags(Process* p, Eterm* n_hstart, Eterm* n_htop, + Eterm* objv, int nobj) +{ + ErlHeapFragment* qb; + char* frag_begin; + Uint frag_size; + ErlMessage* mp; + + /* + * We don't allow references to a heap fragments from the stack, heap, + * or process dictionary. + */ +#ifdef HARDDEBUG + disallow_heap_frag_ref(p, n_htop, p->stop, STACK_START(p) - p->stop); + if (p->dictionary != NULL) { + disallow_heap_frag_ref(p, n_htop, p->dictionary->data, p->dictionary->used); + } + disallow_heap_frag_ref_in_heap(p); +#endif + + /* + * Go through the subset of the root set that is allowed to + * reference data in heap fragments and move data from heap fragments + * to our new heap. + */ + + if (nobj != 0) { + n_htop = collect_root_array(p, n_htop, objv, nobj); + } + if (is_not_immed(p->fvalue)) { + n_htop = collect_root_array(p, n_htop, &p->fvalue, 1); + } + if (is_not_immed(p->ftrace)) { + n_htop = collect_root_array(p, n_htop, &p->ftrace, 1); + } + if (is_not_immed(p->seq_trace_token)) { + n_htop = collect_root_array(p, n_htop, &p->seq_trace_token, 1); + } + if (is_not_immed(p->group_leader)) { + n_htop = collect_root_array(p, n_htop, &p->group_leader, 1); + } + + /* + * Go through the message queue, move everything that is in one of the + * heap fragments to our new heap. + */ + + for (mp = p->msg.first; mp != NULL; mp = mp->next) { + /* + * In most cases, mp->data.attached points to a heap fragment which is + * self-contained and we will copy it to the heap at the + * end of the GC to avoid scanning it. + * + * In a few cases, however, such as in process_info(Pid, messages) + * and trace_delivered/1, a new message points to a term that has + * been allocated by HAlloc() and mp->data.attached is NULL. Therefore + * we need this loop. + */ + if (mp->data.attached == NULL) { + n_htop = collect_root_array(p, n_htop, mp->m, 2); + } + } + + /* + * Now all references in the root set point to the new heap. However, + * many references on the new heap point to heap fragments. + */ + + qb = MBUF(p); + while (qb != NULL) { + frag_begin = (char *) qb->mem; + frag_size = qb->size * sizeof(Eterm); + if (frag_size != 0) { + n_htop = sweep_one_area(n_hstart, n_htop, frag_begin, frag_size); + } + qb = qb->next; + } + return n_htop; +} + +static Uint +setup_rootset(Process *p, Eterm *objv, int nobj, Rootset *rootset) +{ + Uint avail; + Roots* roots; + ErlMessage* mp; + Uint n; + + n = 0; + roots = rootset->roots = rootset->def; + rootset->size = ALENGTH(rootset->def); + + roots[n].v = p->stop; + roots[n].sz = STACK_START(p) - p->stop; + ++n; + + if (p->dictionary != NULL) { + roots[n].v = p->dictionary->data; + roots[n].sz = p->dictionary->used; + ++n; + } + if (nobj > 0) { + roots[n].v = objv; + roots[n].sz = nobj; + ++n; + } + + ASSERT((is_nil(p->seq_trace_token) || + is_tuple(p->seq_trace_token) || + is_atom(p->seq_trace_token))); + if (is_not_immed(p->seq_trace_token)) { + roots[n].v = &p->seq_trace_token; + roots[n].sz = 1; + n++; + } + + ASSERT(is_nil(p->tracer_proc) || + is_internal_pid(p->tracer_proc) || + is_internal_port(p->tracer_proc)); + + ASSERT(is_pid(p->group_leader)); + if (is_not_immed(p->group_leader)) { + roots[n].v = &p->group_leader; + roots[n].sz = 1; + n++; + } + + /* + * The process may be garbage-collected while it is terminating. + * (fvalue contains the EXIT reason and ftrace the saved stack trace.) + */ + if (is_not_immed(p->fvalue)) { + roots[n].v = &p->fvalue; + roots[n].sz = 1; + n++; + } + if (is_not_immed(p->ftrace)) { + roots[n].v = &p->ftrace; + roots[n].sz = 1; + n++; + } + ASSERT(n <= rootset->size); + + mp = p->msg.first; + avail = rootset->size - n; + while (mp != NULL) { + if (avail == 0) { + Uint new_size = 2*rootset->size; + if (roots == rootset->def) { + roots = erts_alloc(ERTS_ALC_T_ROOTSET, + new_size*sizeof(Roots)); + sys_memcpy(roots, rootset->def, sizeof(rootset->def)); + } else { + roots = erts_realloc(ERTS_ALC_T_ROOTSET, + (void *) roots, + new_size*sizeof(Roots)); + } + rootset->size = new_size; + avail = new_size - n; + } + if (mp->data.attached == NULL) { + roots[n].v = mp->m; + roots[n].sz = 2; + n++; + avail--; + } + mp = mp->next; + } + rootset->roots = roots; + rootset->num_roots = n; + return n; +} + +static +void cleanup_rootset(Rootset* rootset) +{ + if (rootset->roots != rootset->def) { + erts_free(ERTS_ALC_T_ROOTSET, rootset->roots); + } +} + +static void +grow_new_heap(Process *p, Uint new_sz, Eterm* objv, int nobj) +{ + Eterm* new_heap; + int heap_size = HEAP_TOP(p) - HEAP_START(p); + int stack_size = p->hend - p->stop; + Sint offs; + + ASSERT(HEAP_SIZE(p) < new_sz); + new_heap = (Eterm *) ERTS_HEAP_REALLOC(ERTS_ALC_T_HEAP, + (void *) HEAP_START(p), + sizeof(Eterm)*(HEAP_SIZE(p)), + sizeof(Eterm)*new_sz); + + if ((offs = new_heap - HEAP_START(p)) == 0) { /* No move. */ + HEAP_END(p) = new_heap + new_sz; + sys_memmove(p->hend - stack_size, p->stop, stack_size * sizeof(Eterm)); + p->stop = p->hend - stack_size; + } else { + char* area = (char *) HEAP_START(p); + Uint area_size = (char *) HEAP_TOP(p) - area; + Eterm* prev_stop = p->stop; + + offset_heap(new_heap, heap_size, offs, area, area_size); + + HIGH_WATER(p) = new_heap + (HIGH_WATER(p) - HEAP_START(p)); + + HEAP_END(p) = new_heap + new_sz; + prev_stop = new_heap + (p->stop - p->heap); + p->stop = p->hend - stack_size; + sys_memmove(p->stop, prev_stop, stack_size * sizeof(Eterm)); + + offset_rootset(p, offs, area, area_size, objv, nobj); + HEAP_TOP(p) = new_heap + heap_size; + HEAP_START(p) = new_heap; + } + HEAP_SIZE(p) = new_sz; +} + +static void +shrink_new_heap(Process *p, Uint new_sz, Eterm *objv, int nobj) +{ + Eterm* new_heap; + int heap_size = HEAP_TOP(p) - HEAP_START(p); + Sint offs; + + int stack_size = p->hend - p->stop; + + ASSERT(new_sz < p->heap_sz); + sys_memmove(p->heap + new_sz - stack_size, p->stop, stack_size * + sizeof(Eterm)); + new_heap = (Eterm *) ERTS_HEAP_REALLOC(ERTS_ALC_T_HEAP, + (void*)p->heap, + sizeof(Eterm)*(HEAP_SIZE(p)), + sizeof(Eterm)*new_sz); + p->hend = new_heap + new_sz; + p->stop = p->hend - stack_size; + + if ((offs = new_heap - HEAP_START(p)) != 0) { + char* area = (char *) HEAP_START(p); + Uint area_size = (char *) HEAP_TOP(p) - area; + + /* + * Normally, we don't expect a shrunk heap to move, but you never + * know on some strange embedded systems... Or when using purify. + */ + + offset_heap(new_heap, heap_size, offs, area, area_size); + + HIGH_WATER(p) = new_heap + (HIGH_WATER(p) - HEAP_START(p)); + offset_rootset(p, offs, area, area_size, objv, nobj); + HEAP_TOP(p) = new_heap + heap_size; + HEAP_START(p) = new_heap; + } + HEAP_SIZE(p) = new_sz; +} + +static Uint +next_vheap_size(Uint vheap, Uint vheap_sz) { + if (vheap < H_MIN_SIZE) { + return H_MIN_SIZE; + } + + /* grow */ + if (vheap > vheap_sz) { + return erts_next_heap_size(2*vheap, 0); + } + /* shrink */ + if ( vheap < vheap_sz/2) { + return (Uint)vheap_sz*3/4; + } + + return vheap_sz; +} + + +static void +sweep_proc_externals(Process *p, int fullsweep) +{ + ExternalThing** prev; + ExternalThing* ptr; + char* oh = 0; + Uint oh_size = 0; + + if (fullsweep == 0) { + oh = (char *) OLD_HEAP(p); + oh_size = (char *) OLD_HEND(p) - oh; + } + + prev = &MSO(p).externals; + ptr = MSO(p).externals; + + while (ptr) { + Eterm* ppt = (Eterm *) ptr; + + if (IS_MOVED(*ppt)) { /* Object is alive */ + ExternalThing* ro = external_thing_ptr(*ppt); + + *prev = ro; /* Patch to moved pos */ + prev = &ro->next; + ptr = ro->next; + } else if (in_area(ppt, oh, oh_size)) { + /* + * Object resides on old heap, and we just did a + * generational collection - keep object in list. + */ + prev = &ptr->next; + ptr = ptr->next; + } else { /* Object has not been moved - deref it */ + erts_deref_node_entry(ptr->node); + *prev = ptr = ptr->next; + } + } + ASSERT(*prev == NULL); +} + +static void +sweep_proc_funs(Process *p, int fullsweep) +{ + ErlFunThing** prev; + ErlFunThing* ptr; + char* oh = 0; + Uint oh_size = 0; + + if (fullsweep == 0) { + oh = (char *) OLD_HEAP(p); + oh_size = (char *) OLD_HEND(p) - oh; + } + + prev = &MSO(p).funs; + ptr = MSO(p).funs; + + while (ptr) { + Eterm* ppt = (Eterm *) ptr; + + if (IS_MOVED(*ppt)) { /* Object is alive */ + ErlFunThing* ro = (ErlFunThing *) fun_val(*ppt); + + *prev = ro; /* Patch to moved pos */ + prev = &ro->next; + ptr = ro->next; + } else if (in_area(ppt, oh, oh_size)) { + /* + * Object resides on old heap, and we just did a + * generational collection - keep object in list. + */ + prev = &ptr->next; + ptr = ptr->next; + } else { /* Object has not been moved - deref it */ + ErlFunEntry* fe = ptr->fe; + + *prev = ptr = ptr->next; + if (erts_refc_dectest(&fe->refc, 0) == 0) { + erts_erase_fun_entry(fe); + } + } + } + ASSERT(*prev == NULL); +} + +struct shrink_cand_data { + ProcBin* new_candidates; + ProcBin* new_candidates_end; + ProcBin* old_candidates; + Uint no_of_candidates; + Uint no_of_active; +}; + +static ERTS_INLINE void +link_live_proc_bin(struct shrink_cand_data *shrink, + ProcBin ***prevppp, + ProcBin **pbpp, + int new_heap) +{ + ProcBin *pbp = *pbpp; + + *pbpp = pbp->next; + + if (pbp->flags & (PB_ACTIVE_WRITER|PB_IS_WRITABLE)) { + ASSERT(((pbp->flags & (PB_ACTIVE_WRITER|PB_IS_WRITABLE)) + == (PB_ACTIVE_WRITER|PB_IS_WRITABLE)) + || ((pbp->flags & (PB_ACTIVE_WRITER|PB_IS_WRITABLE)) + == PB_IS_WRITABLE)); + + + if (pbp->flags & PB_ACTIVE_WRITER) { + pbp->flags &= ~PB_ACTIVE_WRITER; + shrink->no_of_active++; + } + else { /* inactive */ + Uint unused = pbp->val->orig_size - pbp->size; + /* Our allocators are 8 byte aligned, i.e., shrinking with + less than 8 bytes will have no real effect */ + if (unused >= 8) { /* A shrink candidate; save in candidate list */ + if (new_heap) { + if (!shrink->new_candidates) + shrink->new_candidates_end = pbp; + pbp->next = shrink->new_candidates; + shrink->new_candidates = pbp; + } + else { + pbp->next = shrink->old_candidates; + shrink->old_candidates = pbp; + } + shrink->no_of_candidates++; + return; + } + } + } + + /* Not a shrink candidate; keep in original mso list */ + **prevppp = pbp; + *prevppp = &pbp->next; + +} + + +static void +sweep_proc_bins(Process *p, int fullsweep) +{ + struct shrink_cand_data shrink = {0}; + ProcBin** prev; + ProcBin* ptr; + Binary* bptr; + char* oh = NULL; + Uint oh_size = 0; + Uint bin_vheap = 0; + + if (fullsweep == 0) { + oh = (char *) OLD_HEAP(p); + oh_size = (char *) OLD_HEND(p) - oh; + } + + BIN_OLD_VHEAP(p) = 0; + + prev = &MSO(p).mso; + ptr = MSO(p).mso; + + /* + * Note: In R7 we no longer force a fullsweep when we find binaries + * on the old heap. The reason is that with the introduction of the + * bit syntax we can expect binaries to be used a lot more. Note that + * in earlier releases a brand new binary (or any other term) could + * be put on the old heap during a gen-gc fullsweep, but this is + * no longer the case in R7. + */ + while (ptr) { + Eterm* ppt = (Eterm *) ptr; + + if (IS_MOVED(*ppt)) { /* Object is alive */ + bin_vheap += ptr->size / sizeof(Eterm); + ptr = (ProcBin*) binary_val(*ppt); + link_live_proc_bin(&shrink, + &prev, + &ptr, + !in_area(ptr, oh, oh_size)); + } else if (in_area(ppt, oh, oh_size)) { + /* + * Object resides on old heap, and we just did a + * generational collection - keep object in list. + */ + BIN_OLD_VHEAP(p) += ptr->size / sizeof(Eterm); /* for binary gc (words)*/ + link_live_proc_bin(&shrink, &prev, &ptr, 0); + } else { /* Object has not been moved - deref it */ + + *prev = ptr->next; + bptr = ptr->val; + if (erts_refc_dectest(&bptr->refc, 0) == 0) + erts_bin_free(bptr); + ptr = *prev; + } + } + + if (BIN_OLD_VHEAP(p) >= BIN_OLD_VHEAP_SZ(p)) { + FLAGS(p) |= F_NEED_FULLSWEEP; + } + + BIN_VHEAP_SZ(p) = next_vheap_size(bin_vheap, BIN_VHEAP_SZ(p)); + BIN_OLD_VHEAP_SZ(p) = next_vheap_size(BIN_OLD_VHEAP(p), BIN_OLD_VHEAP_SZ(p)); + MSO(p).overhead = bin_vheap; + + /* + * If we got any shrink candidates, check them out. + */ + + if (shrink.no_of_candidates) { + ProcBin *candlist[] = {shrink.new_candidates, shrink.old_candidates}; + Uint leave_unused = 0; + int i; + + if (shrink.no_of_active == 0) { + if (shrink.no_of_candidates <= ERTS_INACT_WR_PB_LEAVE_MUCH_LIMIT) + leave_unused = ERTS_INACT_WR_PB_LEAVE_MUCH_PERCENTAGE; + else if (shrink.no_of_candidates <= ERTS_INACT_WR_PB_LEAVE_LIMIT) + leave_unused = ERTS_INACT_WR_PB_LEAVE_PERCENTAGE; + } + + for (i = 0; i < sizeof(candlist)/sizeof(candlist[0]); i++) { + + for (ptr = candlist[i]; ptr; ptr = ptr->next) { + Uint new_size = ptr->size; + + if (leave_unused) { + new_size += (new_size * 100) / leave_unused; + /* Our allocators are 8 byte aligned, i.e., shrinking with + less than 8 bytes will have no real effect */ + if (new_size + 8 >= ptr->val->orig_size) + continue; + } + + ptr->val = erts_bin_realloc(ptr->val, new_size); + ptr->val->orig_size = new_size; + ptr->bytes = (byte *) ptr->val->orig_bytes; + } + } + + + /* + * We now potentially have the mso list divided into three lists: + * - shrink candidates on new heap (inactive writable with unused data) + * - shrink candidates on old heap (inactive writable with unused data) + * - other binaries (read only + active writable ...) + * + * Put them back together: new candidates -> other -> old candidates + * This order will ensure that the list only refers from new + * generation to old and never from old to new *which is important*. + */ + if (shrink.new_candidates) { + if (prev == &MSO(p).mso) /* empty other binaries list */ + prev = &shrink.new_candidates_end->next; + else + shrink.new_candidates_end->next = MSO(p).mso; + MSO(p).mso = shrink.new_candidates; + } + } + + *prev = shrink.old_candidates; +} + +/* + * Offset pointers into the heap (not stack). + */ + +static void +offset_heap(Eterm* hp, Uint sz, Sint offs, char* area, Uint area_size) +{ + while (sz--) { + Eterm val = *hp; + switch (primary_tag(val)) { + case TAG_PRIMARY_LIST: + case TAG_PRIMARY_BOXED: + if (in_area(ptr_val(val), area, area_size)) { + *hp = offset_ptr(val, offs); + } + hp++; + break; + case TAG_PRIMARY_HEADER: { + Uint tari; + + if (header_is_transparent(val)) { + hp++; + continue; + } + tari = thing_arityval(val); + switch (thing_subtag(val)) { + case REFC_BINARY_SUBTAG: + { + ProcBin* pb = (ProcBin*) hp; + Eterm** uptr = (Eterm **) (void *) &pb->next; + + if (*uptr && in_area((Eterm *)pb->next, area, area_size)) { + *uptr += offs; /* Patch the mso chain */ + } + sz -= tari; + hp += tari + 1; + } + break; + case BIN_MATCHSTATE_SUBTAG: + { + ErlBinMatchState *ms = (ErlBinMatchState*) hp; + ErlBinMatchBuffer *mb = &(ms->mb); + if (in_area(ptr_val(mb->orig), area, area_size)) { + mb->orig = offset_ptr(mb->orig, offs); + mb->base = binary_bytes(mb->orig); + } + sz -= tari; + hp += tari + 1; + } + break; + case FUN_SUBTAG: + { + ErlFunThing* funp = (ErlFunThing *) hp; + Eterm** uptr = (Eterm **) (void *) &funp->next; + + if (*uptr && in_area((Eterm *)funp->next, area, area_size)) { + *uptr += offs; + } + sz -= tari; + hp += tari + 1; + } + break; + case EXTERNAL_PID_SUBTAG: + case EXTERNAL_PORT_SUBTAG: + case EXTERNAL_REF_SUBTAG: + { + ExternalThing* etp = (ExternalThing *) hp; + Eterm** uptr = (Eterm **) (void *) &etp->next; + + if (*uptr && in_area((Eterm *)etp->next, area, area_size)) { + *uptr += offs; + } + sz -= tari; + hp += tari + 1; + } + break; + default: + sz -= tari; + hp += tari + 1; + } + break; + } + default: + hp++; + continue; + } + } +} + +/* + * Offset pointers to heap from stack. + */ + +static void +offset_heap_ptr(Eterm* hp, Uint sz, Sint offs, char* area, Uint area_size) +{ + while (sz--) { + Eterm val = *hp; + switch (primary_tag(val)) { + case TAG_PRIMARY_LIST: + case TAG_PRIMARY_BOXED: + if (in_area(ptr_val(val), area, area_size)) { + *hp = offset_ptr(val, offs); + } + hp++; + break; + default: + hp++; + break; + } + } +} + +static void +offset_off_heap(Process* p, Sint offs, char* area, Uint area_size) +{ + if (MSO(p).mso && in_area((Eterm *)MSO(p).mso, area, area_size)) { + Eterm** uptr = (Eterm**) (void *) &MSO(p).mso; + *uptr += offs; + } + + if (MSO(p).funs && in_area((Eterm *)MSO(p).funs, area, area_size)) { + Eterm** uptr = (Eterm**) (void *) &MSO(p).funs; + *uptr += offs; + } + + if (MSO(p).externals && in_area((Eterm *)MSO(p).externals, area, area_size)) { + Eterm** uptr = (Eterm**) (void *) &MSO(p).externals; + *uptr += offs; + } +} + +/* + * Offset pointers in message queue. + */ +static void +offset_mqueue(Process *p, Sint offs, char* area, Uint area_size) +{ + ErlMessage* mp = p->msg.first; + + while (mp != NULL) { + Eterm mesg = ERL_MESSAGE_TERM(mp); + if (is_value(mesg)) { + switch (primary_tag(mesg)) { + case TAG_PRIMARY_LIST: + case TAG_PRIMARY_BOXED: + if (in_area(ptr_val(mesg), area, area_size)) { + ERL_MESSAGE_TERM(mp) = offset_ptr(mesg, offs); + } + break; + } + } + mesg = ERL_MESSAGE_TOKEN(mp); + if (is_boxed(mesg) && in_area(ptr_val(mesg), area, area_size)) { + ERL_MESSAGE_TOKEN(mp) = offset_ptr(mesg, offs); + } + ASSERT((is_nil(ERL_MESSAGE_TOKEN(mp)) || + is_tuple(ERL_MESSAGE_TOKEN(mp)) || + is_atom(ERL_MESSAGE_TOKEN(mp)))); + mp = mp->next; + } +} + +static void ERTS_INLINE +offset_one_rootset(Process *p, Sint offs, char* area, Uint area_size, + Eterm* objv, int nobj) +{ + if (p->dictionary) { + offset_heap(p->dictionary->data, + p->dictionary->used, + offs, area, area_size); + } + offset_heap_ptr(&p->fvalue, 1, offs, area, area_size); + offset_heap_ptr(&p->ftrace, 1, offs, area, area_size); + offset_heap_ptr(&p->seq_trace_token, 1, offs, area, area_size); + offset_heap_ptr(&p->group_leader, 1, offs, area, area_size); + offset_mqueue(p, offs, area, area_size); + offset_heap_ptr(p->stop, (STACK_START(p) - p->stop), offs, area, area_size); + offset_nstack(p, offs, area, area_size); + if (nobj > 0) { + offset_heap_ptr(objv, nobj, offs, area, area_size); + } + offset_off_heap(p, offs, area, area_size); +} + +static void +offset_rootset(Process *p, Sint offs, char* area, Uint area_size, + Eterm* objv, int nobj) +{ + offset_one_rootset(p, offs, area, area_size, objv, nobj); +} + +#if defined(DEBUG) || defined(ERTS_OFFHEAP_DEBUG) + +static int +within2(Eterm *ptr, Process *p, Eterm *real_htop) +{ + ErlHeapFragment* bp = MBUF(p); + ErlMessage* mp = p->msg.first; + Eterm *htop = real_htop ? real_htop : HEAP_TOP(p); + + if (OLD_HEAP(p) && (OLD_HEAP(p) <= ptr && ptr < OLD_HEND(p))) { + return 1; + } + if (HEAP_START(p) <= ptr && ptr < htop) { + return 1; + } + while (bp != NULL) { + if (bp->mem <= ptr && ptr < bp->mem + bp->size) { + return 1; + } + bp = bp->next; + } + while (mp) { + if (mp->data.attached) { + ErlHeapFragment *hfp; + if (is_value(ERL_MESSAGE_TERM(mp))) + hfp = mp->data.heap_frag; + else if (is_not_nil(ERL_MESSAGE_TOKEN(mp))) + hfp = erts_dist_ext_trailer(mp->data.dist_ext); + else + hfp = NULL; + if (hfp && hfp->mem <= ptr && ptr < hfp->mem + hfp->size) + return 1; + } + mp = mp->next; + } + return 0; +} + +int +within(Eterm *ptr, Process *p) +{ + return within2(ptr, p, NULL); +} + +#endif + +#ifdef ERTS_OFFHEAP_DEBUG + +#define ERTS_CHK_OFFHEAP_ASSERT(EXP) \ +do { \ + if (!(EXP)) \ + erl_exit(ERTS_ABORT_EXIT, \ + "%s:%d: Assertion failed: %s\n", \ + __FILE__, __LINE__, #EXP); \ +} while (0) + +#ifdef ERTS_OFFHEAP_DEBUG_CHK_CIRCULAR_EXTERNAL_LIST +# define ERTS_EXTERNAL_VISITED_BIT ((Eterm) 1 << 31) +#endif + + +void +erts_check_off_heap2(Process *p, Eterm *htop) +{ + Eterm *oheap = (Eterm *) OLD_HEAP(p); + Eterm *ohtop = (Eterm *) OLD_HTOP(p); + int old; + ProcBin *pb; + ErlFunThing *eft; + ExternalThing *et; + + old = 0; + for (pb = MSO(p).mso; pb; pb = pb->next) { + Eterm *ptr = (Eterm *) pb; + long refc = erts_refc_read(&pb->val->refc, 1); + ERTS_CHK_OFFHEAP_ASSERT(refc >= 1); + if (old) { + ERTS_CHK_OFFHEAP_ASSERT(oheap <= ptr && ptr < ohtop); + } + else if (oheap <= ptr && ptr < ohtop) + old = 1; + else { + ERTS_CHK_OFFHEAP_ASSERT(within2(ptr, p, htop)); + } + } + + old = 0; + for (eft = MSO(p).funs; eft; eft = eft->next) { + Eterm *ptr = (Eterm *) eft; + long refc = erts_refc_read(&eft->fe->refc, 1); + ERTS_CHK_OFFHEAP_ASSERT(refc >= 1); + if (old) + ERTS_CHK_OFFHEAP_ASSERT(oheap <= ptr && ptr < ohtop); + else if (oheap <= ptr && ptr < ohtop) + old = 1; + else + ERTS_CHK_OFFHEAP_ASSERT(within2(ptr, p, htop)); + } + + old = 0; + for (et = MSO(p).externals; et; et = et->next) { + Eterm *ptr = (Eterm *) et; + long refc = erts_refc_read(&et->node->refc, 1); + ERTS_CHK_OFFHEAP_ASSERT(refc >= 1); +#ifdef ERTS_OFFHEAP_DEBUG_CHK_CIRCULAR_EXTERNAL_LIST + ERTS_CHK_OFFHEAP_ASSERT(!(et->header & ERTS_EXTERNAL_VISITED_BIT)); +#endif + if (old) + ERTS_CHK_OFFHEAP_ASSERT(oheap <= ptr && ptr < ohtop); + else if (oheap <= ptr && ptr < ohtop) + old = 1; + else + ERTS_CHK_OFFHEAP_ASSERT(within2(ptr, p, htop)); +#ifdef ERTS_OFFHEAP_DEBUG_CHK_CIRCULAR_EXTERNAL_LIST + et->header |= ERTS_EXTERNAL_VISITED_BIT; +#endif + } + +#ifdef ERTS_OFFHEAP_DEBUG_CHK_CIRCULAR_EXTERNAL_LIST + for (et = MSO(p).externals; et; et = et->next) + et->header &= ~ERTS_EXTERNAL_VISITED_BIT; +#endif + +} + +void +erts_check_off_heap(Process *p) +{ + erts_check_off_heap2(p, NULL); +} + +#endif |