blob: f73561b3cd9fb2a9d75101bb0bf62c6f606706f8 [file] [log] [blame]
// 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"
static 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
runtime·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
runtime·throw("finalizer table inconsistent");
return nil;
}
static Fintab fintab;
// add finalizer; caller is responsible for making sure not already in table
void
runtime·addfinalizer(void *p, void (*f)(void*), int32 nret)
{
Fintab newtab;
int32 i;
uint32 *ref;
byte *base;
Finalizer *e;
e = nil;
if(f != nil) {
e = runtime·mal(sizeof *e);
e->fn = f;
e->nret = nret;
}
runtime·lock(&finlock);
if(!runtime·mlookup(p, &base, nil, nil, &ref) || p != base) {
runtime·unlock(&finlock);
runtime·throw("addfinalizer on invalid pointer");
}
if(f == nil) {
if(*ref & RefHasFinalizer) {
lookfintab(&fintab, p, 1);
*ref &= ~RefHasFinalizer;
}
runtime·unlock(&finlock);
return;
}
if(*ref & RefHasFinalizer) {
runtime·unlock(&finlock);
runtime·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 = runtime·mallocgc(newtab.max*sizeof newtab.key[0], RefNoPointers, 0, 1);
newtab.val = runtime·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]);
}
runtime·free(fintab.key);
runtime·free(fintab.val);
fintab = newtab;
}
addfintab(&fintab, p, e);
runtime·unlock(&finlock);
}
// get finalizer; if del, delete finalizer.
// caller is responsible for updating RefHasFinalizer bit.
Finalizer*
runtime·getfinalizer(void *p, bool del)
{
Finalizer *f;
runtime·lock(&finlock);
f = lookfintab(&fintab, p, del);
runtime·unlock(&finlock);
return f;
}
void
runtime·walkfintab(void (*fn)(void*))
{
void **key;
void **ekey;
runtime·lock(&finlock);
key = fintab.key;
ekey = key + fintab.max;
for(; key < ekey; key++)
if(*key != nil && *key != ((void*)-1))
fn(*key);
runtime·unlock(&finlock);
}