racket/src/mzscheme/gc2/mem_account.c
2009-09-10 16:24:18 +00:00

722 lines
20 KiB
C

/*****************************************************************************/
/* 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;
Mark_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);
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);
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(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(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(ptr);
}
}
}
inline static void mark_cust_boxes(NewGC *gc, Scheme_Custodian *cur)
{
Scheme_Object *pr, *prev = NULL, *next;
GC_Weak_Box *wb;
Mark_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);
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)
{
NewGC *gc = GC_get_GC();
if (gc->doing_memory_accounting) {
return OBJPTR_TO_OBJHEAD(p)->size;
}
return gc->mark_table[btc_redirect_thread](p);
}
int BTC_custodian_mark(void *p)
{
NewGC *gc = GC_get_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);
else
return OBJPTR_TO_OBJHEAD(p)->size;
}
return gc->mark_table[btc_redirect_custodian](p);
}
int BTC_cust_box_mark(void *p)
{
NewGC *gc = GC_get_GC();
if (gc->doing_memory_accounting) {
return OBJPTR_TO_OBJHEAD(p)->size;
}
return gc->mark_table[btc_redirect_cust_box](p);
}
inline static void mark_normal_obj(NewGC *gc, int type, void *ptr)
{
switch(type) {
case PAGE_TAGGED: {
/* we do not want to mark the pointers in a thread or custodian
unless the object's owner is the current owner. In the case
of threads, we already used it for roots, so we can just
ignore them outright. In the case of custodians, we do need
to do the check; those differences are handled by replacing
the mark procedure in mark_table. */
unsigned short tag = *(unsigned short*)ptr;
ASSERT_TAG(tag);
if((unsigned long)gc->mark_table[tag] < PAGE_TYPES) {
/* atomic */
} else {
GC_ASSERT(mark_table[tag]);
gc->mark_table[tag](ptr);
}
break;
}
case PAGE_ATOMIC: break;
case PAGE_ARRAY: {
objhead *info = OBJPTR_TO_OBJHEAD(ptr);
void **temp = ptr;
void **end = PPTR(info) + info->size;
while(temp < end) gcMARK(*(temp++));
break;
};
case PAGE_TARRAY: {
objhead *info = OBJPTR_TO_OBJHEAD(ptr);
unsigned short tag = *(unsigned short*)ptr;
void **temp = ptr;
void **end = PPTR(info) + (info->size - INSET_WORDS);
while(temp < end) temp += gc->mark_table[tag](temp);
break;
}
case PAGE_XTAGGED: GC_mark_xtagged(ptr); break;
}
}
inline static void mark_acc_big_page(NewGC *gc, mpage *page)
{
void **start = PPTR(BIG_PAGE_TO_OBJECT(page));
void **end = PPTR(NUM(page->addr) + page->size);
switch(page->page_type) {
case PAGE_TAGGED:
{
unsigned short tag = *(unsigned short*)start;
if((unsigned long)gc->mark_table[tag] < PAGE_TYPES) {
/* atomic */
} else
gc->mark_table[tag](start); break;
}
case PAGE_ATOMIC: break;
case PAGE_ARRAY: while(start < end) gcMARK(*(start++)); break;
case PAGE_XTAGGED: GC_mark_xtagged(start); break;
case PAGE_TARRAY: {
unsigned short tag = *(unsigned short *)start;
end -= INSET_WORDS;
while(start < end) start += gc->mark_table[tag](start);
break;
}
}
}
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)
{
struct mpage *page;
void *p;
PageMap pagemap = gc->page_maps;
while(pop_ptr(&p) && !gc->kill_propagation_loop) {
page = pagemap_find_page(pagemap, p);
#ifdef MZ_USE_PLACES
if (!page) {
page = pagemap_find_page(MASTERGC->page_maps, p);
}
#endif
GC_ASSERT(page);
set_backtrace_source(p, page->page_type);
GCDEBUG((DEBUGOUTF, "btc_account: popped off page %p:%p, ptr %p\n", page, page->addr, p));
if(page->size_class) {
if (page->size_class > 1)
mark_acc_big_page(gc, page);
else {
objhead *info = MED_OBJHEAD(p, page->size);
p = OBJHEAD_TO_OBJPTR(info);
mark_normal_obj(gc, info->type, p);
}
} else
mark_normal_obj(gc, page->page_type, p);
}
if(gc->kill_propagation_loop)
reset_pointer_stack();
}
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();
}
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);
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