Commit fb376021 authored by Keith Randall's avatar Keith Randall

runtime: record type information for hashtable internal structures.

Remove all hashtable-specific GC code.

Fixes bug 6119.

R=cshapiro, dvyukov, khr
CC=golang-dev
https://golang.org/cl/13078044
parent d0206101
......@@ -186,6 +186,10 @@ struct Type
// TARRAY
vlong bound; // negative is dynamic array
// TMAP
Type* bucket; // internal type representing a hash bucket
Type* hmap; // internal type representing a Hmap (map header object)
int32 maplineno; // first use of TFORW as map key
int32 embedlineno; // first use of TFORW as embedded type
......
......@@ -101,6 +101,135 @@ lsort(Sig *l, int(*f)(Sig*, Sig*))
return l;
}
// Builds a type respresenting a Bucket structure for
// the given map type. This type is not visible to users -
// we include only enough information to generate a correct GC
// program for it.
// Make sure this stays in sync with ../../pkg/runtime/hashmap.c!
enum {
BUCKETSIZE = 8,
MAXKEYSIZE = 128,
MAXVALSIZE = 128,
};
static Type*
mapbucket(Type *t)
{
Type *keytype, *valtype;
Type *bucket;
Type *overflowfield, *keysfield, *valuesfield;
int32 offset;
if(t->bucket != T)
return t->bucket;
keytype = t->down;
valtype = t->type;
if(keytype->width > MAXKEYSIZE)
keytype = ptrto(keytype);
if(valtype->width > MAXVALSIZE)
valtype = ptrto(valtype);
bucket = typ(TSTRUCT);
// The first field is: uint8 topbits[BUCKETSIZE].
// We don't need to encode it as GC doesn't care about it.
offset = BUCKETSIZE * 1;
overflowfield = typ(TFIELD);
overflowfield->type = ptrto(bucket);
overflowfield->width = offset; // "width" is offset in structure
overflowfield->sym = mal(sizeof(Sym)); // not important but needs to be set to give this type a name
overflowfield->sym->name = "overflow";
offset += widthptr;
keysfield = typ(TFIELD);
keysfield->type = typ(TARRAY);
keysfield->type->type = keytype;
keysfield->type->bound = BUCKETSIZE;
keysfield->type->width = BUCKETSIZE * keytype->width;
keysfield->width = offset;
keysfield->sym = mal(sizeof(Sym));
keysfield->sym->name = "keys";
offset += BUCKETSIZE * keytype->width;
valuesfield = typ(TFIELD);
valuesfield->type = typ(TARRAY);
valuesfield->type->type = valtype;
valuesfield->type->bound = BUCKETSIZE;
valuesfield->type->width = BUCKETSIZE * valtype->width;
valuesfield->width = offset;
valuesfield->sym = mal(sizeof(Sym));
valuesfield->sym->name = "values";
offset += BUCKETSIZE * valtype->width;
// link up fields
bucket->type = overflowfield;
overflowfield->down = keysfield;
keysfield->down = valuesfield;
valuesfield->down = T;
bucket->width = offset;
bucket->local = t->local;
t->bucket = bucket;
return bucket;
}
// Builds a type respresenting a Hmap structure for
// the given map type. This type is not visible to users -
// we include only enough information to generate a correct GC
// program for it.
// Make sure this stays in sync with ../../pkg/runtime/hashmap.c!
static Type*
hmap(Type *t)
{
Type *h, *bucket;
Type *bucketsfield, *oldbucketsfield;
int32 offset;
if(t->hmap != T)
return t->hmap;
bucket = mapbucket(t);
h = typ(TSTRUCT);
offset = widthint; // count
offset += 4; // flags
offset += 4; // hash0
offset += 1; // B
offset += 1; // keysize
offset += 1; // valuesize
offset = (offset + 1) / 2 * 2;
offset += 2; // bucketsize
offset = (offset + widthptr - 1) / widthptr * widthptr;
bucketsfield = typ(TFIELD);
bucketsfield->type = ptrto(bucket);
bucketsfield->width = offset;
bucketsfield->sym = mal(sizeof(Sym));
bucketsfield->sym->name = "buckets";
offset += widthptr;
oldbucketsfield = typ(TFIELD);
oldbucketsfield->type = ptrto(bucket);
oldbucketsfield->width = offset;
oldbucketsfield->sym = mal(sizeof(Sym));
oldbucketsfield->sym->name = "oldbuckets";
offset += widthptr;
offset += widthptr; // nevacuate (last field in Hmap)
// link up fields
h->type = bucketsfield;
bucketsfield->down = oldbucketsfield;
oldbucketsfield->down = T;
h->width = offset;
h->local = t->local;
t->hmap = h;
return h;
}
/*
* f is method type, with receiver.
* return function type, receiver as first argument (or not).
......@@ -715,7 +844,7 @@ static Sym*
dtypesym(Type *t)
{
int ot, xt, n, isddd, dupok;
Sym *s, *s1, *s2, *slink;
Sym *s, *s1, *s2, *s3, *s4, *slink;
Sig *a, *m;
Type *t1, *tbase, *t2;
......@@ -855,10 +984,14 @@ ok:
// ../../pkg/runtime/type.go:/MapType
s1 = dtypesym(t->down);
s2 = dtypesym(t->type);
s3 = dtypesym(mapbucket(t));
s4 = dtypesym(hmap(t));
ot = dcommontype(s, ot, t);
xt = ot - 2*widthptr;
ot = dsymptr(s, ot, s1, 0);
ot = dsymptr(s, ot, s2, 0);
ot = dsymptr(s, ot, s3, 0);
ot = dsymptr(s, ot, s4, 0);
break;
case TPTR32:
......@@ -1118,9 +1251,9 @@ dgcsym1(Sym *s, int ot, Type *t, vlong *off, int stack_size)
// NOTE: Any changes here need to be made to reflect.MapOf as well.
if(*off % widthptr != 0)
fatal("dgcsym1: invalid alignment, %T", t);
ot = duintptr(s, ot, GC_MAP_PTR);
ot = duintptr(s, ot, GC_PTR);
ot = duintptr(s, ot, *off);
ot = dsymptr(s, ot, dtypesym(t), 0);
ot = dsymptr(s, ot, dgcsym(hmap(t)), 0);
*off += t->width;
break;
......
......@@ -313,9 +313,11 @@ type interfaceType struct {
// mapType represents a map type.
type mapType struct {
rtype `reflect:"map"`
key *rtype // map key type
elem *rtype // map element (value) type
rtype `reflect:"map"`
key *rtype // map key type
elem *rtype // map element (value) type
bucket *rtype // internal bucket structure
hmap *rtype // internal map header
}
// ptrType represents a pointer type.
......@@ -354,7 +356,6 @@ const (
_GC_ARRAY_START
_GC_ARRAY_NEXT
_GC_CALL
_GC_MAP_PTR
_GC_CHAN_PTR
_GC_STRING
_GC_EFACE
......@@ -1400,11 +1401,11 @@ func cachePut(k cacheKey, t *rtype) Type {
return t
}
// garbage collection bytecode program for chan or map.
// garbage collection bytecode program for chan.
// See ../../cmd/gc/reflect.c:/^dgcsym1 and :/^dgcsym.
type chanMapGC struct {
type chanGC struct {
width uintptr // sizeof(map)
op uintptr // _GC_MAP_PTR or _GC_CHAN_PTR
op uintptr // _GC_CHAN_PTR
off uintptr // 0
typ *rtype // map type
end uintptr // _GC_END
......@@ -1467,7 +1468,7 @@ func ChanOf(dir ChanDir, t Type) Type {
ch.uncommonType = nil
ch.ptrToThis = nil
ch.gc = unsafe.Pointer(&chanMapGC{
ch.gc = unsafe.Pointer(&chanGC{
width: ch.size,
op: _GC_CHAN_PTR,
off: 0,
......@@ -1521,17 +1522,11 @@ func MapOf(key, elem Type) Type {
mt.hash = fnv1(etyp.hash, 'm', byte(ktyp.hash>>24), byte(ktyp.hash>>16), byte(ktyp.hash>>8), byte(ktyp.hash))
mt.key = ktyp
mt.elem = etyp
mt.bucket = bucketOf(ktyp, etyp)
mt.hmap = hMapOf(mt.bucket)
mt.uncommonType = nil
mt.ptrToThis = nil
mt.gc = unsafe.Pointer(&chanMapGC{
width: mt.size,
op: _GC_MAP_PTR,
off: 0,
typ: &mt.rtype,
end: _GC_END,
})
// INCORRECT. Uncomment to check that TestMapOfGC and TestMapOfGCValues
// fail when mt.gc is wrong.
//mt.gc = unsafe.Pointer(&badGC{width: mt.size, end: _GC_END})
......@@ -1539,6 +1534,117 @@ func MapOf(key, elem Type) Type {
return cachePut(ckey, &mt.rtype)
}
// Make sure these routines stay in sync with ../../pkg/runtime/hashmap.c!
// These types exist only for GC, so we only fill out GC relevant info.
// Currently, that's just size and the GC program. We also fill in string
// for possible debugging use.
const (
BUCKETSIZE = 8
MAXKEYSIZE = 128
MAXVALSIZE = 128
)
func bucketOf(ktyp, etyp *rtype) *rtype {
if ktyp.size > MAXKEYSIZE {
ktyp = PtrTo(ktyp).(*rtype)
}
if etyp.size > MAXVALSIZE {
etyp = PtrTo(etyp).(*rtype)
}
ptrsize := unsafe.Sizeof(uintptr(0))
gc := make([]uintptr, 1) // first entry is size, filled in at the end
offset := BUCKETSIZE * unsafe.Sizeof(uint8(0)) // topbits
gc = append(gc, _GC_PTR, offset, 0 /*self pointer set below*/) // overflow
offset += ptrsize
// keys
if ktyp.kind&kindNoPointers == 0 {
gc = append(gc, _GC_ARRAY_START, offset, BUCKETSIZE, ktyp.size)
gc = appendGCProgram(gc, ktyp)
gc = append(gc, _GC_ARRAY_NEXT)
}
offset += BUCKETSIZE * ktyp.size
// values
if etyp.kind&kindNoPointers == 0 {
gc = append(gc, _GC_ARRAY_START, offset, BUCKETSIZE, etyp.size)
gc = appendGCProgram(gc, etyp)
gc = append(gc, _GC_ARRAY_NEXT)
}
offset += BUCKETSIZE * etyp.size
gc = append(gc, _GC_END)
gc[0] = offset
gc[3] = uintptr(unsafe.Pointer(&gc[0])) // set self pointer
b := new(rtype)
b.size = offset
b.gc = unsafe.Pointer(&gc[0])
s := "bucket(" + *ktyp.string + "," + *etyp.string + ")"
b.string = &s
return b
}
// Take the GC program for "t" and append it to the GC program "gc".
func appendGCProgram(gc []uintptr, t *rtype) []uintptr {
p := t.gc
p = unsafe.Pointer(uintptr(p) + unsafe.Sizeof(uintptr(0))) // skip size
loop:
for {
var argcnt int
switch *(*uintptr)(p) {
case _GC_END:
// Note: _GC_END not included in append
break loop
case _GC_ARRAY_NEXT:
argcnt = 0
case _GC_APTR, _GC_STRING, _GC_EFACE, _GC_IFACE:
argcnt = 1
case _GC_PTR, _GC_CALL, _GC_CHAN_PTR, _GC_SLICE:
argcnt = 2
case _GC_ARRAY_START, _GC_REGION:
argcnt = 3
default:
panic("unknown GC program op for " + *t.string + ": " + strconv.FormatUint(*(*uint64)(p), 10))
}
for i := 0; i < argcnt+1; i++ {
gc = append(gc, *(*uintptr)(p))
p = unsafe.Pointer(uintptr(p) + unsafe.Sizeof(uintptr(0)))
}
}
return gc
}
func hMapOf(bucket *rtype) *rtype {
ptrsize := unsafe.Sizeof(uintptr(0))
// make gc program & compute hmap size
gc := make([]uintptr, 1) // first entry is size, filled in at the end
offset := unsafe.Sizeof(uint(0)) // count
offset += unsafe.Sizeof(uint32(0)) // flags
offset += unsafe.Sizeof(uint32(0)) // hash0
offset += unsafe.Sizeof(uint8(0)) // B
offset += unsafe.Sizeof(uint8(0)) // keysize
offset += unsafe.Sizeof(uint8(0)) // valuesize
offset = (offset + 1) / 2 * 2
offset += unsafe.Sizeof(uint16(0)) // bucketsize
offset = (offset + ptrsize - 1) / ptrsize * ptrsize
gc = append(gc, _GC_PTR, offset, uintptr(bucket.gc)) // buckets
offset += ptrsize
gc = append(gc, _GC_PTR, offset, uintptr(bucket.gc)) // oldbuckets
offset += ptrsize
offset += ptrsize // nevacuate
gc = append(gc, _GC_END)
gc[0] = offset
h := new(rtype)
h.size = offset
h.gc = unsafe.Pointer(&gc[0])
s := "hmap(" + *bucket.string + ")"
h.string = &s
return h
}
// garbage collection bytecode program for slice of non-zero-length values.
// See ../../cmd/gc/reflect.c:/^dgcsym1 and :/^dgcsym.
type sliceGC struct {
......
This diff is collapsed.
......@@ -2,32 +2,3 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
struct Hmap; /* opaque */
/* Used by the garbage collector */
struct hash_gciter
{
Hmap *h;
int32 phase;
uintptr bucket;
struct Bucket *b;
uintptr i;
};
// this data is used by the garbage collector to keep the map's
// internal structures from being reclaimed. The iterator must
// return in st every live object (ones returned by mallocgc) so
// that those objects won't be collected, and it must return
// every key & value in key_data/val_data so they can get scanned
// for pointers they point to. Note that if you malloc storage
// for keys and values, you need to do both.
struct hash_gciter_data
{
uint8 *st; /* internal structure, or nil */
uint8 *key_data; /* key data, or nil */
uint8 *val_data; /* value data, or nil */
bool indirectkey; /* storing pointers to keys */
bool indirectval; /* storing pointers to values */
};
bool hash_gciter_init (struct Hmap *h, struct hash_gciter *it);
bool hash_gciter_next (struct hash_gciter *it, struct hash_gciter_data *data);
......@@ -487,8 +487,7 @@ enum
{
TypeInfo_SingleObject = 0,
TypeInfo_Array = 1,
TypeInfo_Map = 2,
TypeInfo_Chan = 3,
TypeInfo_Chan = 2,
// Enables type information at the end of blocks allocated from heap
DebugTypeAtBlockEnd = 0,
......
......@@ -176,7 +176,6 @@ static struct {
enum {
GC_DEFAULT_PTR = GC_NUM_INSTR,
GC_MAP_NEXT,
GC_CHAN,
GC_NUM_INSTR2
......@@ -580,9 +579,6 @@ flushobjbuf(Obj *objbuf, Obj **objbufpos, Obj **_wp, Workbuf **_wbuf, uintptr *_
// Program that scans the whole block and treats every block element as a potential pointer
static uintptr defaultProg[2] = {PtrSize, GC_DEFAULT_PTR};
// Hashmap iterator program
static uintptr mapProg[2] = {0, GC_MAP_NEXT};
// Hchan program
static uintptr chanProg[2] = {0, GC_CHAN};
......@@ -622,8 +618,11 @@ checkptr(void *obj, uintptr objti)
}
tisize = *(uintptr*)objti;
// Sanity check for object size: it should fit into the memory block.
if((byte*)obj + tisize > objstart + s->elemsize)
if((byte*)obj + tisize > objstart + s->elemsize) {
runtime·printf("object of type '%S' at %p/%p does not fit in block %p/%p\n",
*t->string, obj, tisize, objstart, s->elemsize);
runtime·throw("invalid gc type info");
}
if(obj != objstart)
return;
// If obj points to the beginning of the memory block,
......@@ -639,7 +638,7 @@ checkptr(void *obj, uintptr objti)
for(j = 1; pc1[j] != GC_END && pc2[j] != GC_END; j++) {
if(pc1[j] != pc2[j]) {
runtime·printf("invalid gc type info for '%s' at %p, type info %p, block info %p\n",
t->string ? (int8*)t->string->str : (int8*)"?", j, pc1[j], pc2[j]);
t->string ? (int8*)t->string->str : (int8*)"?", j, pc1[j], pc2[j]);
runtime·throw("invalid gc type info");
}
}
......@@ -662,7 +661,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
byte *b, *arena_start, *arena_used;
uintptr n, i, end_b, elemsize, size, ti, objti, count, type;
uintptr *pc, precise_type, nominal_size;
uintptr *map_ret, mapkey_size, mapval_size, mapkey_ti, mapval_ti, *chan_ret, chancap;
uintptr *chan_ret, chancap;
void *obj;
Type *t;
Slice *sliceptr;
......@@ -672,11 +671,6 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
Obj *objbuf, *objbuf_end, *objbufpos;
Eface *eface;
Iface *iface;
Hmap *hmap;
MapType *maptype;
bool mapkey_kind, mapval_kind;
struct hash_gciter map_iter;
struct hash_gciter_data d;
Hchan *chan;
ChanType *chantype;
......@@ -705,10 +699,6 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
objbufpos = objbuf;
// (Silence the compiler)
map_ret = nil;
mapkey_size = mapval_size = 0;
mapkey_kind = mapval_kind = false;
mapkey_ti = mapval_ti = 0;
chan = nil;
chantype = nil;
chan_ret = nil;
......@@ -777,23 +767,6 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
stack_top.elemsize = pc[0];
stack_top.loop_or_ret = pc+1;
break;
case TypeInfo_Map:
hmap = (Hmap*)b;
maptype = (MapType*)t;
if(hash_gciter_init(hmap, &map_iter)) {
mapkey_size = maptype->key->size;
mapkey_kind = maptype->key->kind;
mapkey_ti = (uintptr)maptype->key->gc | PRECISE;
mapval_size = maptype->elem->size;
mapval_kind = maptype->elem->kind;
mapval_ti = (uintptr)maptype->elem->gc | PRECISE;
map_ret = nil;
pc = mapProg;
} else {
goto next_block;
}
break;
case TypeInfo_Chan:
chan = (Hchan*)b;
chantype = (ChanType*)t;
......@@ -994,77 +967,6 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
pc = (uintptr*)((byte*)pc + *(int32*)(pc+2)); // target of the CALL instruction
continue;
case GC_MAP_PTR:
hmap = *(Hmap**)(stack_top.b + pc[1]);
if(hmap == nil) {
pc += 3;
continue;
}
if(markonly(hmap)) {
maptype = (MapType*)pc[2];
if(hash_gciter_init(hmap, &map_iter)) {
mapkey_size = maptype->key->size;
mapkey_kind = maptype->key->kind;
mapkey_ti = (uintptr)maptype->key->gc | PRECISE;
mapval_size = maptype->elem->size;
mapval_kind = maptype->elem->kind;
mapval_ti = (uintptr)maptype->elem->gc | PRECISE;
// Start mapProg.
map_ret = pc+3;
pc = mapProg+1;
} else {
pc += 3;
}
} else {
pc += 3;
}
continue;
case GC_MAP_NEXT:
// Add all keys and values to buffers, mark all subtables.
while(hash_gciter_next(&map_iter, &d)) {
// buffers: reserve space for 2 objects.
if(ptrbufpos+2 >= ptrbuf_end)
flushptrbuf(ptrbuf, &ptrbufpos, &wp, &wbuf, &nobj);
if(objbufpos+2 >= objbuf_end)
flushobjbuf(objbuf, &objbufpos, &wp, &wbuf, &nobj);
if(d.st != nil)
markonly(d.st);
if(d.key_data != nil) {
if(!(mapkey_kind & KindNoPointers) || d.indirectkey) {
if(!d.indirectkey)
*objbufpos++ = (Obj){d.key_data, mapkey_size, mapkey_ti};
else {
if(Debug) {
obj = *(void**)d.key_data;
if(!(arena_start <= obj && obj < arena_used))
runtime·throw("scanblock: inconsistent hashmap");
}
*ptrbufpos++ = (PtrTarget){*(void**)d.key_data, mapkey_ti};
}
}
if(!(mapval_kind & KindNoPointers) || d.indirectval) {
if(!d.indirectval)
*objbufpos++ = (Obj){d.val_data, mapval_size, mapval_ti};
else {
if(Debug) {
obj = *(void**)d.val_data;
if(!(arena_start <= obj && obj < arena_used))
runtime·throw("scanblock: inconsistent hashmap");
}
*ptrbufpos++ = (PtrTarget){*(void**)d.val_data, mapval_ti};
}
}
}
}
if(map_ret == nil)
goto next_block;
pc = map_ret;
continue;
case GC_REGION:
obj = (void*)(stack_top.b + pc[1]);
size = pc[2];
......@@ -1077,7 +979,6 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
continue;
case GC_CHAN_PTR:
// Similar to GC_MAP_PTR
chan = *(Hchan**)(stack_top.b + pc[1]);
if(chan == nil) {
pc += 3;
......
......@@ -26,7 +26,6 @@ enum {
GC_ARRAY_START, // Start an array with a fixed length. Args: (off, len, elemsize)
GC_ARRAY_NEXT, // The next element of an array. Args: none
GC_CALL, // Call a subroutine. Args: (off, objgcrel)
GC_MAP_PTR, // Go map. Args: (off, MapType*)
GC_CHAN_PTR, // Go channel. Args: (off, ChanType*)
GC_STRING, // Go string. Args: (off)
GC_EFACE, // interface{}. Args: (off)
......
......@@ -70,6 +70,8 @@ struct MapType
Type;
Type *key;
Type *elem;
Type *bucket; // internal type representing a hash bucket
Type *hmap; // internal type representing a Hmap
};
struct ChanType
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment