/*****************************************************************************/ /* memory accounting */ /*****************************************************************************/ #ifdef NEWGC_BTC_ACCOUNT #include "../src/schpriv.h" /* BTC_ prefixed functions are called by newgc.c */ /* btc_ prefixed functions are internal to mem_account.c */ static const int btc_redirect_thread = 511; static const int btc_redirect_custodian = 510; static const int btc_redirect_ephemeron = 509; static const int btc_redirect_cust_box = 508; /*****************************************************************************/ /* thread list */ /*****************************************************************************/ inline static int current_owner(NewGC *gc, Scheme_Custodian *c); inline static void BTC_register_new_thread(void *t, void *c) { NewGC *gc = GC_get_GC(); GC_Thread_Info *work; work = (GC_Thread_Info *)ofm_malloc(sizeof(GC_Thread_Info)); ((Scheme_Thread *)t)->gc_info = work; work->owner = current_owner(gc, (Scheme_Custodian *)c); work->thread = t; work->next = gc->thread_infos; gc->thread_infos = work; } inline static void BTC_register_thread(void *t, void *c) { NewGC *gc = GC_get_GC(); GC_Thread_Info *work; work = ((Scheme_Thread *)t)->gc_info; work->owner = current_owner(gc, (Scheme_Custodian *)c); } inline static void mark_threads(NewGC *gc, int owner) { GC_Thread_Info *work; Mark2_Proc thread_mark = gc->mark_table[btc_redirect_thread]; for(work = gc->thread_infos; work; work = work->next) if(work->owner == owner) { if (((Scheme_Thread *)work->thread)->running) { thread_mark(work->thread, gc); if (work->thread == scheme_current_thread) { GC_mark_variable_stack(GC_variable_stack, 0, get_stack_base(gc), NULL); } } } } inline static void clean_up_thread_list(NewGC *gc) { GC_Thread_Info *work = gc->thread_infos; GC_Thread_Info *prev = NULL; while(work) { if(!pagemap_find_page(gc->page_maps, work->thread) || marked(gc, work->thread)) { work->thread = GC_resolve(work->thread); prev = work; work = work->next; } else { GC_Thread_Info *next = work->next; if(prev) prev->next = next; if(!prev) gc->thread_infos = next; free(work); work = next; } } } inline static int thread_get_owner(void *p) { return ((Scheme_Thread *)p)->gc_info->owner; } #define OWNER_TABLE_INIT_AMT 10 inline static int create_blank_owner_set(NewGC *gc) { int i; unsigned int curr_size = gc->owner_table_size; OTEntry **owner_table = gc->owner_table; unsigned int old_size; OTEntry **naya; for (i = 1; i < curr_size; i++) { if (!owner_table[i]) { owner_table[i] = ofm_malloc(sizeof(OTEntry)); bzero(owner_table[i], sizeof(OTEntry)); return i; } } old_size = curr_size; if (!curr_size) { curr_size = OWNER_TABLE_INIT_AMT; } else { curr_size *= 2; } gc->owner_table_size = curr_size; naya = (OTEntry **)ofm_malloc(curr_size * sizeof(OTEntry*)); memcpy(naya, owner_table, old_size*sizeof(OTEntry*)); gc->owner_table = owner_table = naya; bzero(((char*)owner_table) + (sizeof(OTEntry*) * old_size), (curr_size - old_size) * sizeof(OTEntry*)); return create_blank_owner_set(gc); } inline static int custodian_to_owner_set(NewGC *gc,Scheme_Custodian *cust) { int i; if (cust->gc_owner_set) return cust->gc_owner_set; i = create_blank_owner_set(gc); gc->owner_table[i]->originator = cust; cust->gc_owner_set = i; return i; } inline static int current_owner(NewGC *gc, Scheme_Custodian *c) { if (!scheme_current_thread) return 1; else if (!c) return thread_get_owner(scheme_current_thread); else return custodian_to_owner_set(gc, c); } void BTC_register_root_custodian(void *_c) { NewGC *gc = GC_get_GC(); Scheme_Custodian *c = (Scheme_Custodian *)_c; if (gc->owner_table) { /* Reset */ free(gc->owner_table); gc->owner_table = NULL; gc->owner_table_size = 0; } if (create_blank_owner_set(gc) != 1) { GCPRINT(GCOUTF, "Something extremely weird (and bad) has happened.\n"); abort(); } gc->owner_table[1]->originator = c; c->gc_owner_set = 1; } inline static int custodian_member_owner_set(NewGC *gc, void *cust, int set) { Scheme_Custodian_Reference *box; Scheme_Custodian *work = (Scheme_Custodian *) gc->owner_table[set]->originator; while(work) { if(work == cust) return 1; box = work->parent; work = box ? SCHEME_PTR1_VAL(box) : NULL; } return 0; } inline static void account_memory(NewGC *gc, int set, long amount) { gc->owner_table[set]->memory_use += amount; } inline static void free_owner_set(NewGC *gc, int set) { OTEntry **owner_table = gc->owner_table; if(owner_table[set]) { free(owner_table[set]); } owner_table[set] = NULL; } inline static void clean_up_owner_table(NewGC *gc) { OTEntry **owner_table = gc->owner_table; const int table_size = gc->owner_table_size; int i; for(i = 1; i < table_size; i++) if(owner_table[i]) { /* repair or delete the originator */ if(!marked(gc, owner_table[i]->originator)) { owner_table[i]->originator = NULL; } else owner_table[i]->originator = GC_resolve(owner_table[i]->originator); /* potential delete */ if(i != 1) if((owner_table[i]->memory_use == 0) && !owner_table[i]->originator) free_owner_set(gc, i); } } inline static unsigned long custodian_usage(NewGC*gc, void *custodian) { OTEntry **owner_table; unsigned long retval = 0; int i; if(!gc->really_doing_accounting) { gc->park[0] = custodian; gc->really_doing_accounting = 1; garbage_collect(gc, 1, 0); custodian = gc->park[0]; gc->park[0] = NULL; } i = custodian_to_owner_set(gc, (Scheme_Custodian *)custodian); owner_table = gc->owner_table; if (owner_table[i]) retval = owner_table[i]->memory_use; else retval = 0; return gcWORDS_TO_BYTES(retval); } inline static void BTC_memory_account_mark(NewGC *gc, mpage *page, void *ptr) { GCDEBUG((DEBUGOUTF, "BTC_memory_account_mark: %p/%p\n", page, ptr)); if(page->size_class) { if(page->size_class > 1) { /* big page */ objhead *info = BIG_PAGE_TO_OBJHEAD(page); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, gcBYTES_TO_WORDS(page->size)); push_ptr(gc, TAG_AS_BIG_PAGE_PTR(ptr)); } } else { /* medium page */ objhead *info = MED_OBJHEAD(ptr, page->size); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, info->size); ptr = OBJHEAD_TO_OBJPTR(info); push_ptr(gc, ptr); } } } else { objhead *info = OBJPTR_TO_OBJHEAD(ptr); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, info->size); push_ptr(gc, ptr); } } } inline static void mark_cust_boxes(NewGC *gc, Scheme_Custodian *cur) { Scheme_Object *pr, *prev = NULL, *next; GC_Weak_Box *wb; Mark2_Proc cust_box_mark = gc->mark_table[btc_redirect_cust_box]; /* cust boxes is a list of weak boxes to cust boxes */ pr = cur->cust_boxes; while (pr) { wb = (GC_Weak_Box *)SCHEME_CAR(pr); next = SCHEME_CDR(pr); if (wb->val) { cust_box_mark(wb->val, gc); prev = pr; } else { if (prev) SCHEME_CDR(prev) = next; else cur->cust_boxes = next; --cur->num_cust_boxes; } pr = next; } cur->checked_cust_boxes = cur->num_cust_boxes; } int BTC_thread_mark(void *p, struct NewGC *gc) { if (gc->doing_memory_accounting) { return OBJPTR_TO_OBJHEAD(p)->size; } return gc->mark_table[btc_redirect_thread](p, gc); } int BTC_custodian_mark(void *p, struct NewGC *gc) { if (gc->doing_memory_accounting) { if(custodian_to_owner_set(gc, p) == gc->current_mark_owner) return gc->mark_table[btc_redirect_custodian](p, gc); else return OBJPTR_TO_OBJHEAD(p)->size; } return gc->mark_table[btc_redirect_custodian](p, gc); } int BTC_cust_box_mark(void *p, struct NewGC *gc) { if (gc->doing_memory_accounting) { return OBJPTR_TO_OBJHEAD(p)->size; } return gc->mark_table[btc_redirect_cust_box](p, gc); } static void btc_overmem_abort(NewGC *gc) { gc->kill_propagation_loop = 1; GCWARN((GCOUTF, "WARNING: Ran out of memory accounting. " "Info will be wrong.\n")); } static void propagate_accounting_marks(NewGC *gc) { void *p; Mark2_Proc *mark_table = gc->mark_table; while(pop_ptr(gc, &p) && !gc->kill_propagation_loop) { /* GCDEBUG((DEBUGOUTF, "btc_account: popped off page %p:%p, ptr %p\n", page, page->addr, p)); */ propagate_marks_worker(gc, mark_table, p); } if(gc->kill_propagation_loop) reset_pointer_stack(gc); } inline static void BTC_initialize_mark_table(NewGC *gc) { gc->mark_table[scheme_thread_type] = BTC_thread_mark; gc->mark_table[scheme_custodian_type] = BTC_custodian_mark; gc->mark_table[gc->ephemeron_tag] = BTC_ephemeron_mark; gc->mark_table[gc->cust_box_tag] = BTC_cust_box_mark; } inline static int BTC_get_redirect_tag(NewGC *gc, int tag) { if (tag == scheme_thread_type ) { tag = btc_redirect_thread; } else if (tag == scheme_custodian_type ) { tag = btc_redirect_custodian; } else if (tag == gc->ephemeron_tag ) { tag = btc_redirect_ephemeron; } else if (tag == gc->cust_box_tag ) { tag = btc_redirect_cust_box; } return tag; } static void BTC_do_accounting(NewGC *gc) { const int table_size = gc->owner_table_size; OTEntry **owner_table = gc->owner_table; if(gc->really_doing_accounting) { Scheme_Custodian *cur = owner_table[current_owner(gc, NULL)]->originator, *last, *parent; Scheme_Custodian_Reference *box = cur->global_next; int i; GCDEBUG((DEBUGOUTF, "\nBEGINNING MEMORY ACCOUNTING\n")); gc->doing_memory_accounting = 1; gc->in_unsafe_allocation_mode = 1; gc->unsafe_allocation_abort = btc_overmem_abort; /* clear the memory use numbers out */ for(i = 1; i < table_size; i++) if(owner_table[i]) owner_table[i]->memory_use = 0; /* start with root: */ while (cur->parent && SCHEME_PTR1_VAL(cur->parent)) { cur = SCHEME_PTR1_VAL(cur->parent); } /* walk forward for the order we want (blame parents instead of children) */ last = cur; while(cur) { int owner = custodian_to_owner_set(gc, cur); gc->current_mark_owner = owner; GCDEBUG((DEBUGOUTF,"MARKING THREADS OF OWNER %i (CUST %p)\n", owner, cur)); gc->kill_propagation_loop = 0; mark_threads(gc, owner); mark_cust_boxes(gc, cur); GCDEBUG((DEBUGOUTF, "Propagating accounting marks\n")); propagate_accounting_marks(gc); last = cur; box = cur->global_next; cur = box ? SCHEME_PTR1_VAL(box) : NULL; } /* walk backward folding totals int parent */ cur = last; while (cur) { int owner = custodian_to_owner_set(gc, cur); box = cur->parent; parent = box ? SCHEME_PTR1_VAL(box) : NULL; if (parent) { int powner = custodian_to_owner_set(gc, parent); owner_table = gc->owner_table; owner_table[powner]->memory_use += owner_table[owner]->memory_use; } box = cur->global_prev; cur = box ? SCHEME_PTR1_VAL(box) : NULL; } gc->in_unsafe_allocation_mode = 0; gc->doing_memory_accounting = 0; gc->old_btc_mark = gc->new_btc_mark; gc->new_btc_mark = !gc->new_btc_mark; } clear_stack_pages(gc); } inline static void BTC_add_account_hook(int type,void *c1,void *c2,unsigned long b) { NewGC *gc = GC_get_GC(); AccountHook *work; if(!gc->really_doing_accounting) { gc->park[0] = c1; gc->park[1] = c2; gc->really_doing_accounting = 1; garbage_collect(gc, 1, 0); c1 = gc->park[0]; gc->park[0] = NULL; c2 = gc->park[1]; gc->park[1] = NULL; } if (type == MZACCT_LIMIT) gc->reset_limits = 1; if (type == MZACCT_REQUIRE) gc->reset_required = 1; for(work = gc->hooks; work; work = work->next) { if((work->type == type) && (work->c2 == c2) && (work->c1 == c1)) { if(type == MZACCT_REQUIRE) { if(b > work->amount) work->amount = b; } else { /* (type == MZACCT_LIMIT) */ if(b < work->amount) work->amount = b; } break; } } if(!work) { work = ofm_malloc(sizeof(AccountHook)); work->type = type; work->c1 = c1; work->c2 = c2; work->amount = b; /* push work onto hooks */ work->next = gc->hooks; gc->hooks = work; } } inline static void clean_up_account_hooks(NewGC *gc) { AccountHook *work = gc->hooks; AccountHook *prev = NULL; while(work) { if((!work->c1 || marked(gc, work->c1)) && marked(gc, work->c2)) { work->c1 = GC_resolve(work->c1); work->c2 = GC_resolve(work->c2); prev = work; work = work->next; } else { /* remove work hook */ AccountHook *next = work->next; if(prev) prev->next = next; if(!prev) gc->hooks = next; free(work); work = next; } } } static unsigned long custodian_super_require(NewGC *gc, void *c) { int set = ((Scheme_Custodian *)c)->gc_owner_set; const int table_size = gc->owner_table_size; OTEntry **owner_table = gc->owner_table; if (gc->reset_required) { int i; for(i = 1; i < table_size; i++) if (owner_table[i]) owner_table[i]->required_set = 0; gc->reset_required = 0; } if (!owner_table[set]->required_set) { unsigned long req = 0, r; AccountHook *work = gc->hooks; while(work) { if ((work->type == MZACCT_REQUIRE) && (c == work->c2)) { r = work->amount + custodian_super_require(gc, work->c1); if (r > req) req = r; } work = work->next; } owner_table[set]->super_required = req; owner_table[set]->required_set = 1; } return owner_table[set]->super_required; } inline static void BTC_run_account_hooks(NewGC *gc) { AccountHook *work = gc->hooks; AccountHook *prev = NULL; while(work) { if( ((work->type == MZACCT_REQUIRE) && ((gc->used_pages > (gc->max_pages_for_use / 2)) || ((((gc->max_pages_for_use / 2) - gc->used_pages) * APAGE_SIZE) < (work->amount + custodian_super_require(gc, work->c1))))) || ((work->type == MZACCT_LIMIT) && (GC_get_memory_use(work->c1) > work->amount))) { AccountHook *next = work->next; if(prev) prev->next = next; if(!prev) gc->hooks = next; scheme_schedule_custodian_close(work->c2); free(work); work = next; } else { prev = work; work = work->next; } } } static unsigned long custodian_single_time_limit(NewGC *gc, int set) { OTEntry **owner_table = gc->owner_table; const int table_size = gc->owner_table_size; if (!set) return (unsigned long)(long)-1; if (gc->reset_limits) { int i; for(i = 1; i < table_size; i++) if (owner_table[i]) owner_table[i]->limit_set = 0; gc->reset_limits = 0; } if (!owner_table[set]->limit_set) { /* Check for limits on this custodian or one of its ancestors: */ unsigned long limit = (unsigned long)(long)-1; Scheme_Custodian *orig = (Scheme_Custodian *) owner_table[set]->originator, *c; AccountHook *work = gc->hooks; while(work) { if ((work->type == MZACCT_LIMIT) && (work->c1 == work->c2)) { c = orig; while (1) { if (work->c2 == c) { if (work->amount < limit) limit = work->amount; break; } if (!c->parent) break; c = (Scheme_Custodian*)SCHEME_PTR1_VAL(c->parent); if (!c) break; } } work = work->next; } owner_table[set]->single_time_limit = limit; owner_table[set]->limit_set = 1; } return owner_table[set]->single_time_limit; } long BTC_get_memory_use(NewGC* gc, void *o) { Scheme_Object *arg = (Scheme_Object*)o; if(SAME_TYPE(SCHEME_TYPE(arg), scheme_custodian_type)) { return custodian_usage(gc, arg); } return 0; } int BTC_single_allocation_limit(NewGC *gc, size_t sizeb) { /* We're allowed to fail. Check for allocations that exceed a single-time * limit. Otherwise, the limit doesn't work as intended, because * a program can allocate a large block that nearly exhausts memory, * and then a subsequent allocation can fail. As long as the limit * is much smaller than the actual available memory, and as long as * GC_out_of_memory protects any user-requested allocation whose size * is independent of any existing object, then we can enforce the limit. */ return (custodian_single_time_limit(gc, thread_get_owner(scheme_current_thread)) < sizeb); } static inline void BTC_clean_up(NewGC *gc) { clean_up_thread_list(gc); clean_up_owner_table(gc); clean_up_account_hooks(gc); } static inline void BTC_set_btc_mark(NewGC *gc, objhead* info) { info->btc_mark = gc->old_btc_mark; } #endif