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

// Implementation of runtime/debug.WriteHeapDump.  Writes all
// objects in the heap plus additional info (roots, threads,
// finalizers, etc.) to a file.

// The format of the dumped file is described at
// http://code.google.com/p/go-wiki/wiki/heapdump13

#include "runtime.h"
#include "arch_GOARCH.h"
#include "malloc.h"
#include "mgc0.h"
#include "type.h"
#include "typekind.h"
#include "funcdata.h"
#include "zaexperiment.h"
#include "../../cmd/ld/textflag.h"

extern byte data[];
extern byte edata[];
extern byte bss[];
extern byte ebss[];
extern byte gcdata[];
extern byte gcbss[];

enum {
	FieldKindEol = 0,
	FieldKindPtr = 1,
	FieldKindString = 2,
	FieldKindSlice = 3,
	FieldKindIface = 4,
	FieldKindEface = 5,

	TagEOF = 0,
	TagObject = 1,
	TagOtherRoot = 2,
	TagType = 3,
	TagGoRoutine = 4,
	TagStackFrame = 5,
	TagParams = 6,
	TagFinalizer = 7,
	TagItab = 8,
	TagOSThread = 9,
	TagMemStats = 10,
	TagQueuedFinalizer = 11,
	TagData = 12,
	TagBss = 13,
	TagDefer = 14,
	TagPanic = 15,
	TagMemProf = 16,
	TagAllocSample = 17,

	TypeInfo_Conservative = 127,
};

static uintptr* playgcprog(uintptr offset, uintptr *prog, void (*callback)(void*,uintptr,uintptr), void *arg);
static void dumpfields(uintptr *prog);
static void dumpefacetypes(void *obj, uintptr size, Type *type, uintptr kind);
static void dumpbvtypes(BitVector *bv, byte *base);

// fd to write the dump to.
static uintptr dumpfd;

// buffer of pending write data
enum {
	BufSize = 4096,
};
#pragma dataflag NOPTR
static byte buf[BufSize];
static uintptr nbuf;

static void
write(byte *data, uintptr len)
{
	if(len + nbuf <= BufSize) {
		runtime·memmove(buf + nbuf, data, len);
		nbuf += len;
		return;
	}
	runtime·write(dumpfd, buf, nbuf);
	if(len >= BufSize) {
		runtime·write(dumpfd, data, len);
		nbuf = 0;
	} else {
		runtime·memmove(buf, data, len);
		nbuf = len;
	}
}

static void
flush(void)
{
	runtime·write(dumpfd, buf, nbuf);
	nbuf = 0;
}

// Cache of types that have been serialized already.
// We use a type's hash field to pick a bucket.
// Inside a bucket, we keep a list of types that
// have been serialized so far, most recently used first.
// Note: when a bucket overflows we may end up
// serializing a type more than once.  That's ok.
enum {
	TypeCacheBuckets = 256, // must be a power of 2
	TypeCacheAssoc = 4,
};
typedef struct TypeCacheBucket TypeCacheBucket;
struct TypeCacheBucket {
	Type *t[TypeCacheAssoc];
};
static TypeCacheBucket typecache[TypeCacheBuckets];

// dump a uint64 in a varint format parseable by encoding/binary
static void
dumpint(uint64 v)
{
	byte buf[10];
	int32 n;
	n = 0;
	while(v >= 0x80) {
		buf[n++] = v | 0x80;
		v >>= 7;
	}
	buf[n++] = v;
	write(buf, n);
}

static void
dumpbool(bool b)
{
	dumpint(b ? 1 : 0);
}

// dump varint uint64 length followed by memory contents
static void
dumpmemrange(byte *data, uintptr len)
{
	dumpint(len);
	write(data, len);
}

static void
dumpstr(String s)
{
	dumpmemrange(s.str, s.len);
}

static void
dumpcstr(int8 *c)
{
	dumpmemrange((byte*)c, runtime·findnull((byte*)c));
}

// dump information for a type
static void
dumptype(Type *t)
{
	TypeCacheBucket *b;
	int32 i, j;

	if(t == nil) {
		return;
	}

	// If we've definitely serialized the type before,
	// no need to do it again.
	b = &typecache[t->hash & (TypeCacheBuckets-1)];
	if(t == b->t[0]) return;
	for(i = 1; i < TypeCacheAssoc; i++) {
		if(t == b->t[i]) {
			// Move-to-front
			for(j = i; j > 0; j--) {
				b->t[j] = b->t[j-1];
			}
			b->t[0] = t;
			return;
		}
	}
	// Might not have been dumped yet.  Dump it and
	// remember we did so.
	for(j = TypeCacheAssoc-1; j > 0; j--) {
		b->t[j] = b->t[j-1];
	}
	b->t[0] = t;
	
	// dump the type
	dumpint(TagType);
	dumpint((uintptr)t);
	dumpint(t->size);
	if(t->x == nil || t->x->pkgPath == nil || t->x->name == nil) {
		dumpstr(*t->string);
	} else {
		dumpint(t->x->pkgPath->len + 1 + t->x->name->len);
		write(t->x->pkgPath->str, t->x->pkgPath->len);
		write((byte*)".", 1);
		write(t->x->name->str, t->x->name->len);
	}
	dumpbool(t->size > PtrSize || (t->kind & KindNoPointers) == 0);
	dumpfields((uintptr*)t->gc + 1);
}

// returns true if object is scannable
static bool
scannable(byte *obj)
{
	uintptr *b, off, shift;

	off = (uintptr*)obj - (uintptr*)runtime·mheap.arena_start;  // word offset
	b = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
	shift = off % wordsPerBitmapWord;
	return ((*b >> shift) & bitScan) != 0;
}

// dump an object
static void
dumpobj(byte *obj, uintptr size, Type *type, uintptr kind)
{
	if(type != nil) {
		dumptype(type);
		dumpefacetypes(obj, size, type, kind);
	}

	dumpint(TagObject);
	dumpint((uintptr)obj);
	dumpint((uintptr)type);
	dumpint(kind);
	dumpmemrange(obj, size);
}

static void
dumpotherroot(int8 *description, byte *to)
{
	dumpint(TagOtherRoot);
	dumpcstr(description);
	dumpint((uintptr)to);
}

static void
dumpfinalizer(byte *obj, FuncVal *fn, Type* fint, PtrType *ot)
{
	dumpint(TagFinalizer);
	dumpint((uintptr)obj);
	dumpint((uintptr)fn);
	dumpint((uintptr)fn->fn);
	dumpint((uintptr)fint);
	dumpint((uintptr)ot);
}

typedef struct ChildInfo ChildInfo;
struct ChildInfo {
	// Information passed up from the callee frame about
	// the layout of the outargs region.
	uintptr argoff;     // where the arguments start in the frame
	uintptr arglen;     // size of args region
	BitVector args;    // if args.n >= 0, pointer map of args region

	byte *sp;           // callee sp
	uintptr depth;      // depth in call stack (0 == most recent)
};

// dump kinds & offsets of interesting fields in bv
static void
dumpbv(BitVector *bv, uintptr offset)
{
	uintptr i;

	for(i = 0; i < bv->n; i += BitsPerPointer) {
		switch(bv->data[i/32] >> i%32 & 3) {
		case BitsDead:
		case BitsScalar:
			break;
		case BitsPointer:
			dumpint(FieldKindPtr);
			dumpint(offset + i / BitsPerPointer * PtrSize);
			break;
		case BitsMultiWord:
			switch(bv->data[(i+BitsPerPointer)/32] >> (i+BitsPerPointer)%32 & 3) {
			case BitsString:
				dumpint(FieldKindString);
				dumpint(offset + i / BitsPerPointer * PtrSize);
				i += BitsPerPointer;
				break;
			case BitsSlice:
				dumpint(FieldKindSlice);
				dumpint(offset + i / BitsPerPointer * PtrSize);
				i += 2 * BitsPerPointer;
				break;
			case BitsIface:
				dumpint(FieldKindIface);
				dumpint(offset + i / BitsPerPointer * PtrSize);
				i += BitsPerPointer;
				break;
			case BitsEface:
				dumpint(FieldKindEface);
				dumpint(offset + i / BitsPerPointer * PtrSize);
				i += BitsPerPointer;
				break;
			}
		}
	}
}

static bool
dumpframe(Stkframe *s, void *arg)
{
	Func *f;
	ChildInfo *child;
	uintptr pc, off, size;
	int32 pcdata;
	StackMap *stackmap;
	int8 *name;
	BitVector bv;

	child = (ChildInfo*)arg;
	f = s->fn;

	// Figure out what we can about our stack map
	pc = s->pc;
	if(pc != f->entry)
		pc--;
	pcdata = runtime·pcdatavalue(f, PCDATA_StackMapIndex, pc);
	if(pcdata == -1) {
		// We do not have a valid pcdata value but there might be a
		// stackmap for this function.  It is likely that we are looking
		// at the function prologue, assume so and hope for the best.
		pcdata = 0;
	}
	stackmap = runtime·funcdata(f, FUNCDATA_LocalsPointerMaps);

	// Dump any types we will need to resolve Efaces.
	if(child->args.n >= 0)
		dumpbvtypes(&child->args, (byte*)s->sp + child->argoff);
	if(stackmap != nil && stackmap->n > 0) {
		bv = runtime·stackmapdata(stackmap, pcdata);
		dumpbvtypes(&bv, s->varp - bv.n / BitsPerPointer * PtrSize);
	} else {
		bv.n = -1;
	}

	// Dump main body of stack frame.
	dumpint(TagStackFrame);
	dumpint(s->sp); // lowest address in frame
	dumpint(child->depth); // # of frames deep on the stack
	dumpint((uintptr)child->sp); // sp of child, or 0 if bottom of stack
	dumpmemrange((byte*)s->sp, s->fp - s->sp);  // frame contents
	dumpint(f->entry);
	dumpint(s->pc);
	dumpint(s->continpc);
	name = runtime·funcname(f);
	if(name == nil)
		name = "unknown function";
	dumpcstr(name);

	// Dump fields in the outargs section
	if(child->args.n >= 0) {
		dumpbv(&child->args, child->argoff);
	} else {
		// conservative - everything might be a pointer
		for(off = child->argoff; off < child->argoff + child->arglen; off += PtrSize) {
			dumpint(FieldKindPtr);
			dumpint(off);
		}
	}

	// Dump fields in the local vars section
	if(stackmap == nil) {
		// No locals information, dump everything.
		for(off = child->arglen; off < s->varp - (byte*)s->sp; off += PtrSize) {
			dumpint(FieldKindPtr);
			dumpint(off);
		}
	} else if(stackmap->n < 0) {
		// Locals size information, dump just the locals.
		size = -stackmap->n;
		for(off = s->varp - size - (byte*)s->sp; off < s->varp - (byte*)s->sp; off += PtrSize) {
			dumpint(FieldKindPtr);
			dumpint(off);
		}
	} else if(stackmap->n > 0) {
		// Locals bitmap information, scan just the pointers in
		// locals.
		dumpbv(&bv, s->varp - bv.n / BitsPerPointer * PtrSize - (byte*)s->sp);
	}
	dumpint(FieldKindEol);

	// Record arg info for parent.
	child->argoff = s->argp - (byte*)s->fp;
	child->arglen = s->arglen;
	child->sp = (byte*)s->sp;
	child->depth++;
	stackmap = runtime·funcdata(f, FUNCDATA_ArgsPointerMaps);
	if(stackmap != nil)
		child->args = runtime·stackmapdata(stackmap, pcdata);
	else
		child->args.n = -1;
	return true;
}

static void
dumpgoroutine(G *gp)
{
	uintptr sp, pc, lr;
	ChildInfo child;
	Defer *d;
	Panic *p;

	if(gp->syscallstack != (uintptr)nil) {
		sp = gp->syscallsp;
		pc = gp->syscallpc;
		lr = 0;
	} else {
		sp = gp->sched.sp;
		pc = gp->sched.pc;
		lr = gp->sched.lr;
	}

	dumpint(TagGoRoutine);
	dumpint((uintptr)gp);
	dumpint((uintptr)sp);
	dumpint(gp->goid);
	dumpint(gp->gopc);
	dumpint(gp->status);
	dumpbool(gp->issystem);
	dumpbool(gp->isbackground);
	dumpint(gp->waitsince);
	dumpcstr(gp->waitreason);
	dumpint((uintptr)gp->sched.ctxt);
	dumpint((uintptr)gp->m);
	dumpint((uintptr)gp->defer);
	dumpint((uintptr)gp->panic);

	// dump stack
	child.args.n = -1;
	child.arglen = 0;
	child.sp = nil;
	child.depth = 0;
	if(!ScanStackByFrames)
		runtime·throw("need frame info to dump stacks");
	runtime·gentraceback(pc, sp, lr, gp, 0, nil, 0x7fffffff, dumpframe, &child, false);

	// dump defer & panic records
	for(d = gp->defer; d != nil; d = d->link) {
		dumpint(TagDefer);
		dumpint((uintptr)d);
		dumpint((uintptr)gp);
		dumpint((uintptr)d->argp);
		dumpint((uintptr)d->pc);
		dumpint((uintptr)d->fn);
		dumpint((uintptr)d->fn->fn);
		dumpint((uintptr)d->link);
	}
	for (p = gp->panic; p != nil; p = p->link) {
		dumpint(TagPanic);
		dumpint((uintptr)p);
		dumpint((uintptr)gp);
		dumpint((uintptr)p->arg.type);
		dumpint((uintptr)p->arg.data);
		dumpint((uintptr)p->defer);
		dumpint((uintptr)p->link);
	}
}

static void
dumpgs(void)
{
	G *gp;
	uint32 i;

	// goroutines & stacks
	for(i = 0; i < runtime·allglen; i++) {
		gp = runtime·allg[i];
		switch(gp->status){
		default:
			runtime·printf("unexpected G.status %d\n", gp->status);
			runtime·throw("mark - bad status");
		case Gdead:
			break;
		case Grunnable:
		case Gsyscall:
		case Gwaiting:
			dumpgoroutine(gp);
			break;
		}
	}
}

static void
finq_callback(FuncVal *fn, byte *obj, uintptr nret, Type *fint, PtrType *ot)
{
	dumpint(TagQueuedFinalizer);
	dumpint((uintptr)obj);
	dumpint((uintptr)fn);
	dumpint((uintptr)fn->fn);
	dumpint((uintptr)fint);
	dumpint((uintptr)ot);
	USED(&nret);
}


static void
dumproots(void)
{
	MSpan *s, **allspans;
	uint32 spanidx;
	Special *sp;
	SpecialFinalizer *spf;
	byte *p;

	// data segment
	dumpint(TagData);
	dumpint((uintptr)data);
	dumpmemrange(data, edata - data);
	dumpfields((uintptr*)gcdata + 1);

	// bss segment
	dumpint(TagBss);
	dumpint((uintptr)bss);
	dumpmemrange(bss, ebss - bss);
	dumpfields((uintptr*)gcbss + 1);
	
	// MSpan.types
	allspans = runtime·mheap.allspans;
	for(spanidx=0; spanidx<runtime·mheap.nspan; spanidx++) {
		s = allspans[spanidx];
		if(s->state == MSpanInUse) {
			// The garbage collector ignores type pointers stored in MSpan.types:
			//  - Compiler-generated types are stored outside of heap.
			//  - The reflect package has runtime-generated types cached in its data structures.
			//    The garbage collector relies on finding the references via that cache.
			switch(s->types.compression) {
			case MTypes_Empty:
			case MTypes_Single:
				break;
			case MTypes_Words:
			case MTypes_Bytes:
				dumpotherroot("runtime type info", (byte*)s->types.data);
				break;
			}

			// Finalizers
			for(sp = s->specials; sp != nil; sp = sp->next) {
				if(sp->kind != KindSpecialFinalizer)
					continue;
				spf = (SpecialFinalizer*)sp;
				p = (byte*)((s->start << PageShift) + spf->offset);
				dumpfinalizer(p, spf->fn, spf->fint, spf->ot);
			}
		}
	}

	// Finalizer queue
	runtime·iterate_finq(finq_callback);
}

// Bit vector of free marks.
// Needs to be as big as the largest number of objects per span.
static byte free[PageSize/8];

static void
dumpobjs(void)
{
	uintptr i, j, size, n, off, shift, *bitp, bits, ti, kind;
	MSpan *s;
	MLink *l;
	byte *p;
	Type *t;

	for(i = 0; i < runtime·mheap.nspan; i++) {
		s = runtime·mheap.allspans[i];
		if(s->state != MSpanInUse)
			continue;
		p = (byte*)(s->start << PageShift);
		size = s->elemsize;
		n = (s->npages << PageShift) / size;
		if(n > PageSize/8)
			runtime·throw("free array doesn't have enough entries");
		for(l = s->freelist; l != nil; l = l->next) {
			free[((byte*)l - p) / size] = true;
		}
		for(j = 0; j < n; j++, p += size) {
			if(free[j]) {
				free[j] = false;
				continue;
			}
			off = (uintptr*)p - (uintptr*)runtime·mheap.arena_start;
			bitp = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
			shift = off % wordsPerBitmapWord;
			bits = *bitp >> shift;

			// Skip FlagNoGC allocations (stacks)
			if((bits & bitAllocated) == 0)
				continue;

			// extract type and kind
			ti = runtime·gettype(p);
			t = (Type*)(ti & ~(uintptr)(PtrSize-1));
			kind = ti & (PtrSize-1);
			
			// dump it
			if(kind == TypeInfo_Chan)
				t = ((ChanType*)t)->elem; // use element type for chan encoding
			if(t == nil && scannable(p))
				kind = TypeInfo_Conservative; // special kind for conservatively scanned objects
			dumpobj(p, size, t, kind);
		}
	}
}

static void
dumpparams(void)
{
	byte *x;

	dumpint(TagParams);
	x = (byte*)1;
	if(*(byte*)&x == 1)
		dumpbool(false); // little-endian ptrs
	else
		dumpbool(true); // big-endian ptrs
	dumpint(PtrSize);
	dumpint(runtime·Hchansize);
	dumpint((uintptr)runtime·mheap.arena_start);
	dumpint((uintptr)runtime·mheap.arena_used);
	dumpint(thechar);
	dumpcstr(GOEXPERIMENT);
	dumpint(runtime·ncpu);
}

static void
itab_callback(Itab *tab)
{
	Type *t;

	dumpint(TagItab);
	dumpint((uintptr)tab);
	t = tab->type;
	dumpbool(t->size > PtrSize || (t->kind & KindNoPointers) == 0);
}

static void
dumpitabs(void)
{
	runtime·iterate_itabs(itab_callback);
}

static void
dumpms(void)
{
	M *mp;

	for(mp = runtime·allm; mp != nil; mp = mp->alllink) {
		dumpint(TagOSThread);
		dumpint((uintptr)mp);
		dumpint(mp->id);
		dumpint(mp->procid);
	}
}

static void
dumpmemstats(void)
{
	int32 i;

	dumpint(TagMemStats);
	dumpint(mstats.alloc);
	dumpint(mstats.total_alloc);
	dumpint(mstats.sys);
	dumpint(mstats.nlookup);
	dumpint(mstats.nmalloc);
	dumpint(mstats.nfree);
	dumpint(mstats.heap_alloc);
	dumpint(mstats.heap_sys);
	dumpint(mstats.heap_idle);
	dumpint(mstats.heap_inuse);
	dumpint(mstats.heap_released);
	dumpint(mstats.heap_objects);
	dumpint(mstats.stacks_inuse);
	dumpint(mstats.stacks_sys);
	dumpint(mstats.mspan_inuse);
	dumpint(mstats.mspan_sys);
	dumpint(mstats.mcache_inuse);
	dumpint(mstats.mcache_sys);
	dumpint(mstats.buckhash_sys);
	dumpint(mstats.gc_sys);
	dumpint(mstats.other_sys);
	dumpint(mstats.next_gc);
	dumpint(mstats.last_gc);
	dumpint(mstats.pause_total_ns);
	for(i = 0; i < 256; i++)
		dumpint(mstats.pause_ns[i]);
	dumpint(mstats.numgc);
}

static void
dumpmemprof_callback(Bucket *b, uintptr nstk, uintptr *stk, uintptr size, uintptr allocs, uintptr frees)
{
	uintptr i, pc;
	Func *f;
	byte buf[20];
	String file;
	int32 line;

	dumpint(TagMemProf);
	dumpint((uintptr)b);
	dumpint(size);
	dumpint(nstk);
	for(i = 0; i < nstk; i++) {
		pc = stk[i];
		f = runtime·findfunc(pc);
		if(f == nil) {
			runtime·snprintf(buf, sizeof(buf), "%X", (uint64)pc);
			dumpcstr((int8*)buf);
			dumpcstr("?");
			dumpint(0);
		} else {
			dumpcstr(runtime·funcname(f));
			// TODO: Why do we need to back up to a call instruction here?
			// Maybe profiler should do this.
			if(i > 0 && pc > f->entry) {
				if(thechar == '6' || thechar == '8')
					pc--;
				else
					pc -= 4; // arm, etc
			}
			line = runtime·funcline(f, pc, &file);
			dumpstr(file);
			dumpint(line);
		}
	}
	dumpint(allocs);
	dumpint(frees);
}

static void
dumpmemprof(void)
{
	MSpan *s, **allspans;
	uint32 spanidx;
	Special *sp;
	SpecialProfile *spp;
	byte *p;

	runtime·iterate_memprof(dumpmemprof_callback);

	allspans = runtime·mheap.allspans;
	for(spanidx=0; spanidx<runtime·mheap.nspan; spanidx++) {
		s = allspans[spanidx];
		if(s->state != MSpanInUse)
			continue;
		for(sp = s->specials; sp != nil; sp = sp->next) {
			if(sp->kind != KindSpecialProfile)
				continue;
			spp = (SpecialProfile*)sp;
			p = (byte*)((s->start << PageShift) + spp->offset);
			dumpint(TagAllocSample);
			dumpint((uintptr)p);
			dumpint((uintptr)spp->b);
		}
	}
}

static void
mdump(G *gp)
{
	byte *hdr;
	uintptr i;
	MSpan *s;

	// make sure we're done sweeping
	for(i = 0; i < runtime·mheap.nspan; i++) {
		s = runtime·mheap.allspans[i];
		if(s->state == MSpanInUse)
			runtime·MSpan_EnsureSwept(s);
	}

	runtime·memclr((byte*)&typecache[0], sizeof(typecache));
	hdr = (byte*)"go1.3 heap dump\n";
	write(hdr, runtime·findnull(hdr));
	dumpparams();
	dumpitabs();
	dumpobjs();
	dumpgs();
	dumpms();
	dumproots();
	dumpmemstats();
	dumpmemprof();
	dumpint(TagEOF);
	flush();

	gp->param = nil;
	gp->status = Grunning;
	runtime·gogo(&gp->sched);
}

void
runtime∕debug·WriteHeapDump(uintptr fd)
{
	// Stop the world.
	runtime·semacquire(&runtime·worldsema, false);
	m->gcing = 1;
	m->locks++;
	runtime·stoptheworld();

	// Update stats so we can dump them.
	// As a side effect, flushes all the MCaches so the MSpan.freelist
	// lists contain all the free objects.
	runtime·updatememstats(nil);

	// Set dump file.
	dumpfd = fd;

	// Call dump routine on M stack.
	g->status = Gwaiting;
	g->waitreason = "dumping heap";
	runtime·mcall(mdump);

	// Reset dump file.
	dumpfd = 0;

	// Start up the world again.
	m->gcing = 0;
	runtime·semrelease(&runtime·worldsema);
	runtime·starttheworld();
	m->locks--;
}

// Runs the specified gc program.  Calls the callback for every
// pointer-like field specified by the program and passes to the
// callback the kind and offset of that field within the object.
// offset is the offset in the object of the start of the program.
// Returns a pointer to the opcode that ended the gc program (either
// GC_END or GC_ARRAY_NEXT).
static uintptr*
playgcprog(uintptr offset, uintptr *prog, void (*callback)(void*,uintptr,uintptr), void *arg)
{
	uintptr len, elemsize, i, *end;

	for(;;) {
		switch(prog[0]) {
		case GC_END:
			return prog;
		case GC_PTR:
			callback(arg, FieldKindPtr, offset + prog[1]);
			prog += 3;
			break;
		case GC_APTR:
			callback(arg, FieldKindPtr, offset + prog[1]);
			prog += 2;
			break;
		case GC_ARRAY_START:
			len = prog[2];
			elemsize = prog[3];
			end = nil;
			for(i = 0; i < len; i++) {
				end = playgcprog(offset + prog[1] + i * elemsize, prog + 4, callback, arg);
				if(end[0] != GC_ARRAY_NEXT)
					runtime·throw("GC_ARRAY_START did not have matching GC_ARRAY_NEXT");
			}
			prog = end + 1;
			break;
		case GC_ARRAY_NEXT:
			return prog;
		case GC_CALL:
			playgcprog(offset + prog[1], (uintptr*)((byte*)prog + *(int32*)&prog[2]), callback, arg);
			prog += 3;
			break;
		case GC_CHAN_PTR:
			callback(arg, FieldKindPtr, offset + prog[1]);
			prog += 3;
			break;
		case GC_STRING:
			callback(arg, FieldKindString, offset + prog[1]);
			prog += 2;
			break;
		case GC_EFACE:
			callback(arg, FieldKindEface, offset + prog[1]);
			prog += 2;
			break;
		case GC_IFACE:
			callback(arg, FieldKindIface, offset + prog[1]);
			prog += 2;
			break;
		case GC_SLICE:
			callback(arg, FieldKindSlice, offset + prog[1]);
			prog += 3;
			break;
		case GC_REGION:
			playgcprog(offset + prog[1], (uintptr*)prog[3] + 1, callback, arg);
			prog += 4;
			break;
		default:
			runtime·printf("%D\n", (uint64)prog[0]);
			runtime·throw("bad gc op");
		}
	}
}

static void
dump_callback(void *p, uintptr kind, uintptr offset)
{
	USED(&p);
	dumpint(kind);
	dumpint(offset);
}

// dumpint() the kind & offset of each field in an object.
static void
dumpfields(uintptr *prog)
{
	playgcprog(0, prog, dump_callback, nil);
	dumpint(FieldKindEol);
}

static void
dumpeface_callback(void *p, uintptr kind, uintptr offset)
{
	Eface *e;

	if(kind != FieldKindEface)
		return;
	e = (Eface*)((byte*)p + offset);
	dumptype(e->type);
}

// The heap dump reader needs to be able to disambiguate
// Eface entries.  So it needs to know every type that might
// appear in such an entry.  The following two routines accomplish
// that.

// Dump all the types that appear in the type field of
// any Eface contained in obj.
static void
dumpefacetypes(void *obj, uintptr size, Type *type, uintptr kind)
{
	uintptr i;

	switch(kind) {
	case TypeInfo_SingleObject:
		playgcprog(0, (uintptr*)type->gc + 1, dumpeface_callback, obj);
		break;
	case TypeInfo_Array:
		for(i = 0; i <= size - type->size; i += type->size)
			playgcprog(i, (uintptr*)type->gc + 1, dumpeface_callback, obj);
		break;
	case TypeInfo_Chan:
		if(type->size == 0) // channels may have zero-sized objects in them
			break;
		for(i = runtime·Hchansize; i <= size - type->size; i += type->size)
			playgcprog(i, (uintptr*)type->gc + 1, dumpeface_callback, obj);
		break;
	}
}

// Dump all the types that appear in the type field of
// any Eface described by this bit vector.
static void
dumpbvtypes(BitVector *bv, byte *base)
{
	uintptr i;

	for(i = 0; i < bv->n; i += BitsPerPointer) {
		if((bv->data[i/32] >> i%32 & 3) != BitsMultiWord)
			continue;
		switch(bv->data[(i+BitsPerPointer)/32] >> (i+BitsPerPointer)%32 & 3) {
		case BitsString:
		case BitsIface:
			i += BitsPerPointer;
			break;
		case BitsSlice:
			i += 2 * BitsPerPointer;
			break;
		case BitsEface:
			dumptype(*(Type**)(base + i / BitsPerPointer * PtrSize));
			i += BitsPerPointer;
			break;
		}
	}
}
