blob: 977e6301e6f7c2ef7abf85588b489c92fb8fd342 [file] [log] [blame]
// 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 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
finclosure(void *v)
{
byte *p;
ClosureFreeList *f;
f = v;
p = clos.code + f->index*ClosureSize;
*codeptr(p) = nil;
lock(&clos);
f->next = clos.free;
clos.free = f;
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
ยท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)
throw("bad closure size");
ret = (byte**)((byte*)&arg0 + siz);
if(siz > 100) {
// TODO(rsc): implement stack growth preamble?
throw("closure too big");
}
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;
mheap.closure_min = clos.code;
mheap.closure_max = rodata;
}
if(clos.ecode+ClosureChunk > rodata) {
// Last ditch effort: garbage collect and hope.
unlock(&clos);
gc(1);
lock(&clos);
if(clos.free != nil)
goto alloc;
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 = 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 = 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 = 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.
mcpy(p, closasm, ClosureSize);
*(ClosureData***)(p+2) = block+i;
*(ClosureData***)(p+32+2) = block+i;
p += ClosureSize;
}
if(p != clos.buf+sizeof clos.buf)
throw("bad buf math in closure");
e = dyncode_copy(clos.ecode, clos.buf, ClosureChunk);
if(e != 0) {
fd = 2;
printf("dyncode_copy: error %d\n", e);
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 = mal(sizeof(*d)+siz);
d->free = f;
d->fn = fn;
d->siz = siz;
mcpy((byte*)(d+1), (byte*)&arg0, siz);
*codeptr(p) = d;
addfinalizer(f, finclosure, 0);
unlock(&clos);
*ret = p;
}