shithub: neoventi

ref: a4f141bd3a1af74816c85f8266ee1125437698eb
dir: /cache.c/

View raw version
#include <u.h>
#include <libc.h>

#pragma pack on

typedef struct {
	Lock;
	// arena-local block index | arena index << 16
	u32int index[8];
	u8int values[8*3];
} bucket_t;
#pragma pack off

static char *data;
static bucket_t *buckets, *bucketend;
static u32int *freelist;
static u32int freelen;
static Lock freelock;

#define BUCKET_SIZE 8
// Hardcode 2GiB of cache for now
#define CACHE_SIZE 2ULL*1024*1024*1024
// 
#define NENTRIES CACHE_SIZE/0x2000ULL
// Triple needed size so max possible load factor is ⅓
#define NBUCKETS (3ULL*NENTRIES/8)
#define BITS 64-15

void
cacheinit(void)
{
	usize displacement;
	assert(sizeof(Lock) == 8);
	assert(sizeof(bucket_t) == 64);
	data = sbrk((usize)0x2000 * NENTRIES);
	if(data == (void*)-1)
		sysfatal("failed to allocate cache space of %lux", 8192*NENTRIES);
	buckets = sbrk(64 + 64*NBUCKETS);
	freelist = sbrk(4 * NENTRIES);
	displacement = ((usize)buckets & 0b111111);
	if(displacement != 0){
		displacement = 64 - displacement;
		buckets = (bucket_t*)((usize)buckets + displacement);
	}
	// alignment
	assert(((usize)buckets&0b111111) == 0);
	bucketend = buckets + NBUCKETS;
	freelen = NENTRIES;
	for(u32int i = 0; i < freelen; i += 1){
		freelist[i] = i;
	}
}

// Grabs a free cache entry, evicting if necessary.
u32int
grabfree(void)
{
	u32int result;
	lock(&freelock);
	// Should be impossible to run out - we set up
	// enough buffers for each cache entry to get
	// one.
	// Before grabbing a new one, we'll always evict
	// an old one.
	assert(freelen != 0);
	freelen -= 1;
	result = freelist[freelen];
	unlock(&freelock);
	return result;
}

static bucket_t*
getbucket(u32int key)
{
	// Fibonacci hashing!
	return &buckets[key * 11400714819323198485LL >> BITS];
}

static void
put(bucket_t *bucket, int i, u32int key, u32int cacheindex)
{
	bucket->index[i] = key;
	bucket->values[i*3] = cacheindex&0xFF;
	bucket->values[i*3+1] = (cacheindex>>8)&0xFF;
	bucket->values[i*3+2] = cacheindex>>16;
}

static void
get(bucket_t *bucket, int i, char **buf)
{
	usize cacheindex = bucket->values[i*3] | (bucket->values[i*3+1]<<8) | (bucket->values[i*3+2]<<16);
	*buf = data + cacheindex * 8192;
}

static int
trybucket(bucket_t *bucket, int *i, u32int key)
{
	for(*i = 0; *i < BUCKET_SIZE; *i += 1){
		if(bucket->index[*i] == key)
			return 1;
		if(bucket->index[*i] == 0)
			return 0;
	}
	return -1;
}

// Looks up a cache entry. If the entry is not present in cache,
// this grabs a free buffer, inserts it into cache, and yields
// its index so that the caller may fill it up.
// Returns whether the data was already in cache.
int
cachelookup(char **buf, u16int arenaindex, u32int blockindex)
{
	u32int cacheindex;
	int i;
	u32int key = (arenaindex << 16) | blockindex;
	bucket_t *bucket = getbucket(key);
	usize tried = 0;
	while(tried < NBUCKETS){
		switch(trybucket(bucket, &i, key)){
		case -1:
			// miss. linear probe.
			tried+=1;
			bucket++;
			if(bucket == bucketend)
				bucket = buckets;
			continue;
		case 0:
			// found an empty slot
			cacheindex = grabfree();
			*buf = data+(usize)cacheindex*8192;
			put(bucket, i, key, cacheindex);
			return 0;
		case 1:
			// already exists
			get(bucket, i, buf);
			return 1;
		}
	}
	sysfatal("TODO: support eviction");
	return 0;
}