#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/ns.h>

typedef struct Tag Tag;

struct Tag
{
	ulong	tag;
	ulong	val;
	Proc		*proc;
	Tag		*hash;
	Tag		*free;
};

enum
{
	NHASH = 32
};

static	Tag	*ht[NHASH];
static	Tag	*ft;
static	Lock	rendlock;

ulong
rendezvous(ulong tag, ulong val)
{
	uint h;
	ulong ret;
	Tag *t, **l;
	Proc *p;

	lock(&rendlock);
	h = tag&(NHASH-1);
	for(l=&ht[h]; t=*l; l=&(*l)->hash){
		if(t->tag == tag){
			ret = t->val;
			t->val = val;
			p = t->proc;
			*l = t->hash;
			t->proc = nil;
			pm_wake(p);
			/* lock passes to process being wakened */
			return ret;
		}
	}

	if((t=ft) == nil){
		t = mallocz(sizeof(*t), 1);
		assert(t != nil);
	}else
		ft = ft->free;

	t->tag = tag;
	t->val = val;
	t->hash = *l;
	t->proc = getproc();
	*l = t;
	unlock(&rendlock);

	/*
	 * there's a potential race here: if we get woken before we nap,
	 * the wake may be a no-op.  thus nap only naps for small
	 * amounts of time and we poll t->tag.  since we're using
	 * rendezvous as our synchronization primitive, i'm hesitant
	 * to build still other primitives and build rendezvous with them.
	 * losing this race is not at all likely: the time between
	 * unlocking the lock and napping is only a handful of instructions.
	 *
	 * depending on the implementation of pm_wake, the race may
	 * not be there at all: on some systems, pm_wake waits for the process
	 * to nap before waking it.
	 */
	while(t->proc != nil)
		pm_nap();

	/* lock inherited from waker, tag is unlinked from hash list */
	ret = t->val;
	t->free = ft;
	ft = t;
	unlock(&rendlock);
	return ret;
}
