| // Copyright 2010 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| #include "runtime.h" | |
| #include "malloc.h" | |
| Lock finlock; | |
| // Finalizer hash table. Direct hash, linear scan, at most 3/4 full. | |
| // Table size is power of 3 so that hash can be key % max. | |
| // Key[i] == (void*)-1 denotes free but formerly occupied entry | |
| // (doesn't stop the linear scan). | |
| // Key and val are separate tables because the garbage collector | |
| // must be instructed to ignore the pointers in key but follow the | |
| // pointers in val. | |
| typedef struct Fintab Fintab; | |
| struct Fintab | |
| { | |
| void **key; | |
| Finalizer **val; | |
| int32 nkey; // number of non-nil entries in key | |
| int32 ndead; // number of dead (-1) entries in key | |
| int32 max; // size of key, val allocations | |
| }; | |
| static void | |
| addfintab(Fintab *t, void *k, Finalizer *v) | |
| { | |
| int32 i, j; | |
| i = (uintptr)k % (uintptr)t->max; | |
| for(j=0; j<t->max; j++) { | |
| if(t->key[i] == nil) { | |
| t->nkey++; | |
| goto ret; | |
| } | |
| if(t->key[i] == (void*)-1) { | |
| t->ndead--; | |
| goto ret; | |
| } | |
| if(++i == t->max) | |
| i = 0; | |
| } | |
| // cannot happen - table is known to be non-full | |
| throw("finalizer table inconsistent"); | |
| ret: | |
| t->key[i] = k; | |
| t->val[i] = v; | |
| } | |
| static Finalizer* | |
| lookfintab(Fintab *t, void *k, bool del) | |
| { | |
| int32 i, j; | |
| Finalizer *v; | |
| if(t->max == 0) | |
| return nil; | |
| i = (uintptr)k % (uintptr)t->max; | |
| for(j=0; j<t->max; j++) { | |
| if(t->key[i] == nil) | |
| return nil; | |
| if(t->key[i] == k) { | |
| v = t->val[i]; | |
| if(del) { | |
| t->key[i] = (void*)-1; | |
| t->val[i] = nil; | |
| t->ndead++; | |
| } | |
| return v; | |
| } | |
| if(++i == t->max) | |
| i = 0; | |
| } | |
| // cannot happen - table is known to be non-full | |
| throw("finalizer table inconsistent"); | |
| return nil; | |
| } | |
| static Fintab fintab; | |
| // add finalizer; caller is responsible for making sure not already in table | |
| void | |
| addfinalizer(void *p, void (*f)(void*), int32 nret) | |
| { | |
| Fintab newtab; | |
| int32 i; | |
| uint32 *ref; | |
| byte *base; | |
| Finalizer *e; | |
| e = nil; | |
| if(f != nil) { | |
| e = mal(sizeof *e); | |
| e->fn = f; | |
| e->nret = nret; | |
| } | |
| lock(&finlock); | |
| if(!mlookup(p, &base, nil, nil, &ref) || p != base) { | |
| unlock(&finlock); | |
| throw("addfinalizer on invalid pointer"); | |
| } | |
| if(f == nil) { | |
| if(*ref & RefHasFinalizer) { | |
| lookfintab(&fintab, p, 1); | |
| *ref &= ~RefHasFinalizer; | |
| } | |
| unlock(&finlock); | |
| return; | |
| } | |
| if(*ref & RefHasFinalizer) { | |
| unlock(&finlock); | |
| throw("double finalizer"); | |
| } | |
| *ref |= RefHasFinalizer; | |
| if(fintab.nkey >= fintab.max/2+fintab.max/4) { | |
| // keep table at most 3/4 full: | |
| // allocate new table and rehash. | |
| runtime_memclr((byte*)&newtab, sizeof newtab); | |
| newtab.max = fintab.max; | |
| if(newtab.max == 0) | |
| newtab.max = 3*3*3; | |
| else if(fintab.ndead < fintab.nkey/2) { | |
| // grow table if not many dead values. | |
| // otherwise just rehash into table of same size. | |
| newtab.max *= 3; | |
| } | |
| newtab.key = mallocgc(newtab.max*sizeof newtab.key[0], RefNoPointers, 0, 1); | |
| newtab.val = mallocgc(newtab.max*sizeof newtab.val[0], 0, 0, 1); | |
| for(i=0; i<fintab.max; i++) { | |
| void *k; | |
| k = fintab.key[i]; | |
| if(k != nil && k != (void*)-1) | |
| addfintab(&newtab, k, fintab.val[i]); | |
| } | |
| free(fintab.key); | |
| free(fintab.val); | |
| fintab = newtab; | |
| } | |
| addfintab(&fintab, p, e); | |
| unlock(&finlock); | |
| } | |
| // get finalizer; if del, delete finalizer. | |
| // caller is responsible for updating RefHasFinalizer bit. | |
| Finalizer* | |
| getfinalizer(void *p, bool del) | |
| { | |
| Finalizer *f; | |
| lock(&finlock); | |
| f = lookfintab(&fintab, p, del); | |
| unlock(&finlock); | |
| return f; | |
| } | |
| void | |
| walkfintab(void (*fn)(void*)) | |
| { | |
| void **key; | |
| void **ekey; | |
| lock(&finlock); | |
| key = fintab.key; | |
| ekey = key + fintab.max; | |
| for(; key < ekey; key++) | |
| if(*key != nil && *key != ((void*)-1)) | |
| fn(*key); | |
| unlock(&finlock); | |
| } |