// Copyright 2009 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.

/*
 * Closure implementation for Native Client.
 * Native Client imposes some interesting restrictions.
 *
 * First, we can only add new code to the code segment
 * through a special system call, and we have to pick the
 * maximum amount of code we're going to add that way
 * at link time (8l reserves 512 kB for us).
 *
 * Second, once we've added the code we can't ever
 * change it or delete it.  If we want to garbage collect
 * the memory and then reuse it for another closure,
 * we have to do so without editing the code.
 *
 * To address both of these, we fill the code segment pieces
 * with very stylized closures.  Each has the form given below
 * in the comments on the closasm array, with ** replaced by
 * a pointer to a single word of memory.  The garbage collector
 * treats a pointer to such a closure as equivalent to the value
 * held in **.  This tiled run of closures is called the closure array.
 *
 * The ptr points at a ClosureData structure, defined below,
 * which gives the function, arguments, and size for the
 * closuretramp function.  The ClosureData structure has
 * in it a pointer to a ClosureFreeList structure holding the index
 * of the closure in the closure array (but not a pointer to it). 
 * That structure has a finalizer: when the garbage collector
 * notices that the ClosureFreeList structure is not referenced
 * anymore, that means the closure is not referenced, so it
 * can be reused.  To do that, the ClosureFreeList entry is put
 * onto an actual free list.
 */
#include "runtime.h"
#include "malloc.h"

// NaCl system call to copy data into text segment.
extern int32 runtime·dyncode_copy(void*, void*, int32);

enum{
	// Allocate chunks of 4096 bytes worth of closures:
	// at 64 bytes each, that's 64 closures.
	ClosureChunk = 4096,
	ClosureSize = 64,
};

typedef struct ClosureFreeList ClosureFreeList;
struct ClosureFreeList
{
	ClosureFreeList *next;
	int32 index;	// into closure array
};

// Known to closasm
typedef struct ClosureData ClosureData;
struct ClosureData
{
	ClosureFreeList *free;
	byte *fn;
	int32 siz;
	// then args
};

// List of the closure data pointer blocks we've allocated
// and hard-coded in the closure text segments.
// The list keeps the pointer blocks from getting collected.
typedef struct ClosureDataList ClosureDataList;
struct ClosureDataList
{
	ClosureData **block;
	ClosureDataList *next;
};

static struct {
	Lock;
	byte *code;
	byte *ecode;
	ClosureFreeList *free;
	ClosureDataList *datalist;
	byte buf[ClosureChunk];
} clos;

static byte closasm[64] = {
	0x8b, 0x1d, 0, 0, 0, 0,	// MOVL **, BX
	0x8b, 0x4b, 8,		// MOVL 8(BX), CX
	0x8d, 0x73, 12,		// LEAL 12(BX), SI
	0x29, 0xcc,		// SUBL CX, SP
	0x89, 0xe7,		// MOVL SP, DI
	0xc1, 0xe9, 2,		// SHRL $2, CX
	0xf3, 0xa5,		// REP MOVSL
	0x8b, 0x5b, 4,		// MOVL 4(BX), BX
	0x90, 0x90, 0x90,	// NOP...
	0x83, 0xe3, ~31,	// ANDL $~31, BX
	0xff, 0xd3,		// CALL *BX
	// --- 32-byte boundary
	0x8b, 0x1d, 0, 0, 0, 0,	// MOVL **, BX
	0x03, 0x63, 8,		// ADDL 8(BX), SP
	0x5b,			// POPL BX
	0x83, 0xe3, ~31,	// ANDL $~31, BX
	0xff, 0xe3,		// JMP *BX
	0xf4,			// HLT...
	0xf4, 0xf4, 0xf4, 0xf4,
	0xf4, 0xf4, 0xf4, 0xf4,
	0xf4, 0xf4, 0xf4, 0xf4,
	0xf4, 0xf4, 0xf4, 0xf4,
	// --- 32-byte boundary
};

// Returns immediate pointer from closure code block.
// Triple pointer:
//	p is the instruction stream
//	p+2 is the location of the immediate value
//	*(p+2) is the immediate value, a word in the pointer block
//		permanently associated with this closure.
//	**(p+2) is the ClosureData* pointer temporarily associated
//		with this closure.
//
#define codeptr(p) *(ClosureData***)((byte*)(p)+2)

void
runtime·finclosure(void *v)
{
	byte *p;
	ClosureFreeList *f;

	f = v;
	p = clos.code + f->index*ClosureSize;
	*codeptr(p) = nil;

	runtime·lock(&clos);
	f->next = clos.free;
	clos.free = f;
	runtime·unlock(&clos);
}

#pragma textflag 7
// func closure(siz int32,
//	fn func(arg0, arg1, arg2 *ptr, callerpc uintptr, xxx) yyy,
//	arg0, arg1, arg2 *ptr) (func(xxx) yyy)
void
runtime·closure(int32 siz, byte *fn, byte *arg0)
{
	byte *p, **ret;
	int32 e, i, n, off;
	extern byte rodata[], etext[];
	ClosureData *d, **block;
	ClosureDataList *l;
	ClosureFreeList *f;

	if(siz < 0 || siz%4 != 0)
		runtime·throw("bad closure size");

	ret = (byte**)((byte*)&arg0 + siz);

	if(siz > 100) {
		// TODO(rsc): implement stack growth preamble?
		runtime·throw("closure too big");
	}

	runtime·lock(&clos);
	if(clos.free == nil) {
		// Allocate more closures.
		if(clos.code == nil) {
			// First time: find closure space, between end of text
			// segment and beginning of data.
			clos.code = (byte*)(((uintptr)etext + 65535) & ~65535);
			clos.ecode = clos.code;
			runtime·mheap.closure_min = clos.code;
			runtime·mheap.closure_max = rodata;
		}
		if(clos.ecode+ClosureChunk > rodata) {
			// Last ditch effort: garbage collect and hope.
			runtime·unlock(&clos);
			runtime·gc(1);
			runtime·lock(&clos);
			if(clos.free != nil)
				goto alloc;
			runtime·throw("ran out of room for closures in text segment");
		}

		n = ClosureChunk/ClosureSize;
		
		// Allocate the pointer block as opaque to the
		// garbage collector.  Finalizers will clean up.
		block = runtime·mallocgc(n*sizeof block[0], RefNoPointers, 1, 1);

		// Pointers into the pointer block are getting added
		// to the text segment; keep a pointer here in the data
		// segment so that the garbage collector doesn't free
		// the block itself.
		l = runtime·mal(sizeof *l);
		l->block = block;
		l->next = clos.datalist;
		clos.datalist = l;

		p = clos.buf;
		off = (clos.ecode - clos.code)/ClosureSize;
		for(i=0; i<n; i++) {
			f = runtime·mal(sizeof *f);
			f->index = off++;
			f->next = clos.free;
			clos.free = f;

			// There are two hard-coded immediate values in
			// the assembly that need to be pp+i, one 2 bytes in
			// and one 2 bytes after the 32-byte boundary.
			runtime·mcpy(p, closasm, ClosureSize);
			*(ClosureData***)(p+2) = block+i;
			*(ClosureData***)(p+32+2) = block+i;
			p += ClosureSize;
		}

		if(p != clos.buf+sizeof clos.buf)
			runtime·throw("bad buf math in closure");

		e = runtime·dyncode_copy(clos.ecode, clos.buf, ClosureChunk);
		if(e != 0) {
			runtime·fd = 2;
			if(e == -22)
				runtime·throw("NaCl running with dyncode_copy disabled; export NACLDYNCODE=1 in your environment");
			runtime·printf("dyncode_copy: error %d\n", e);
			runtime·throw("dyncode_copy");
		}
		clos.ecode += ClosureChunk;
	}

alloc:
	// Grab a free closure and save the data pointer in its indirect pointer.
	f = clos.free;
	clos.free = f->next;
	f->next = nil;
	p = clos.code + f->index*ClosureSize;

	d = runtime·mal(sizeof(*d)+siz);
	d->free = f;
	d->fn = fn;
	d->siz = siz;
	runtime·mcpy((byte*)(d+1), (byte*)&arg0, siz);
	*codeptr(p) = d;
	runtime·addfinalizer(f, runtime·finclosure, 0);
	runtime·unlock(&clos);

	*ret = p;
}


