58f7dab40d
This replaces mem.go and the C runtime_ReadMemStats function with the Go 1.7 mstats.go. The GCStats code is commented out for now. The corresponding gccgo code is in runtime/mgc0.c. The variables memstats and worldsema are shared between the Go code and the C code, but are not exported. To make this work, add temporary accessor functions acquireWorldsema, releaseWorldsema, getMstats (the latter known as mstats in the C code). Check the preemptoff field of m when allocating and when considering whether to start a GC. This works with the new stopTheWorld and startTheWorld functions in Go, which are essentially the Go 1.7 versions. Change the compiler to stack allocate closures when compiling the runtime package. Within the runtime packages closures do not escape. This is similar to what the gc compiler does, except that the gc compiler, when compiling the runtime package, gives an error if escape analysis shows that a closure does escape. I added this here because the Go version of ReadMemStats calls systemstack with a closure, and having that allocate memory was causing some tests that measure memory allocations to fail. Reviewed-on: https://go-review.googlesource.com/30972 From-SVN: r241124
958 lines
25 KiB
C
958 lines
25 KiB
C
// 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.
|
|
|
|
// Page heap.
|
|
//
|
|
// See malloc.h for overview.
|
|
//
|
|
// When a MSpan is in the heap free list, state == MSpanFree
|
|
// and heapmap(s->start) == span, heapmap(s->start+s->npages-1) == span.
|
|
//
|
|
// When a MSpan is allocated, state == MSpanInUse
|
|
// and heapmap(i) == span for all s->start <= i < s->start+s->npages.
|
|
|
|
#include "runtime.h"
|
|
#include "arch.h"
|
|
#include "malloc.h"
|
|
|
|
static MSpan *MHeap_AllocLocked(MHeap*, uintptr, int32);
|
|
static bool MHeap_Grow(MHeap*, uintptr);
|
|
static void MHeap_FreeLocked(MHeap*, MSpan*);
|
|
static MSpan *MHeap_AllocLarge(MHeap*, uintptr);
|
|
static MSpan *BestFit(MSpan*, uintptr, MSpan*);
|
|
|
|
static void
|
|
RecordSpan(void *vh, byte *p)
|
|
{
|
|
MHeap *h;
|
|
MSpan *s;
|
|
MSpan **all;
|
|
uint32 cap;
|
|
|
|
h = vh;
|
|
s = (MSpan*)p;
|
|
if(h->nspan >= h->nspancap) {
|
|
cap = 64*1024/sizeof(all[0]);
|
|
if(cap < h->nspancap*3/2)
|
|
cap = h->nspancap*3/2;
|
|
all = (MSpan**)runtime_SysAlloc(cap*sizeof(all[0]), &mstats()->other_sys);
|
|
if(all == nil)
|
|
runtime_throw("runtime: cannot allocate memory");
|
|
if(h->allspans) {
|
|
runtime_memmove(all, h->allspans, h->nspancap*sizeof(all[0]));
|
|
// Don't free the old array if it's referenced by sweep.
|
|
// See the comment in mgc0.c.
|
|
if(h->allspans != runtime_mheap.sweepspans)
|
|
runtime_SysFree(h->allspans, h->nspancap*sizeof(all[0]), &mstats()->other_sys);
|
|
}
|
|
h->allspans = all;
|
|
h->nspancap = cap;
|
|
}
|
|
h->allspans[h->nspan++] = s;
|
|
}
|
|
|
|
// Initialize the heap; fetch memory using alloc.
|
|
void
|
|
runtime_MHeap_Init(MHeap *h)
|
|
{
|
|
MStats *pmstats;
|
|
uint32 i;
|
|
|
|
pmstats = mstats();
|
|
runtime_FixAlloc_Init(&h->spanalloc, sizeof(MSpan), RecordSpan, h, &pmstats->mspan_sys);
|
|
runtime_FixAlloc_Init(&h->cachealloc, sizeof(MCache), nil, nil, &pmstats->mcache_sys);
|
|
runtime_FixAlloc_Init(&h->specialfinalizeralloc, sizeof(SpecialFinalizer), nil, nil, &pmstats->other_sys);
|
|
runtime_FixAlloc_Init(&h->specialprofilealloc, sizeof(SpecialProfile), nil, nil, &pmstats->other_sys);
|
|
// h->mapcache needs no init
|
|
for(i=0; i<nelem(h->free); i++) {
|
|
runtime_MSpanList_Init(&h->free[i]);
|
|
runtime_MSpanList_Init(&h->busy[i]);
|
|
}
|
|
runtime_MSpanList_Init(&h->freelarge);
|
|
runtime_MSpanList_Init(&h->busylarge);
|
|
for(i=0; i<nelem(h->central); i++)
|
|
runtime_MCentral_Init(&h->central[i], i);
|
|
}
|
|
|
|
void
|
|
runtime_MHeap_MapSpans(MHeap *h)
|
|
{
|
|
uintptr pagesize;
|
|
uintptr n;
|
|
|
|
// Map spans array, PageSize at a time.
|
|
n = (uintptr)h->arena_used;
|
|
n -= (uintptr)h->arena_start;
|
|
n = n / PageSize * sizeof(h->spans[0]);
|
|
n = ROUND(n, PageSize);
|
|
pagesize = getpagesize();
|
|
n = ROUND(n, pagesize);
|
|
if(h->spans_mapped >= n)
|
|
return;
|
|
runtime_SysMap((byte*)h->spans + h->spans_mapped, n - h->spans_mapped, h->arena_reserved, &mstats()->other_sys);
|
|
h->spans_mapped = n;
|
|
}
|
|
|
|
// Sweeps spans in list until reclaims at least npages into heap.
|
|
// Returns the actual number of pages reclaimed.
|
|
static uintptr
|
|
MHeap_ReclaimList(MHeap *h, MSpan *list, uintptr npages)
|
|
{
|
|
MSpan *s;
|
|
uintptr n;
|
|
uint32 sg;
|
|
|
|
n = 0;
|
|
sg = runtime_mheap.sweepgen;
|
|
retry:
|
|
for(s = list->next; s != list; s = s->next) {
|
|
if(s->sweepgen == sg-2 && runtime_cas(&s->sweepgen, sg-2, sg-1)) {
|
|
runtime_MSpanList_Remove(s);
|
|
// swept spans are at the end of the list
|
|
runtime_MSpanList_InsertBack(list, s);
|
|
runtime_unlock(h);
|
|
n += runtime_MSpan_Sweep(s);
|
|
runtime_lock(h);
|
|
if(n >= npages)
|
|
return n;
|
|
// the span could have been moved elsewhere
|
|
goto retry;
|
|
}
|
|
if(s->sweepgen == sg-1) {
|
|
// the span is being sweept by background sweeper, skip
|
|
continue;
|
|
}
|
|
// already swept empty span,
|
|
// all subsequent ones must also be either swept or in process of sweeping
|
|
break;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Sweeps and reclaims at least npage pages into heap.
|
|
// Called before allocating npage pages.
|
|
static void
|
|
MHeap_Reclaim(MHeap *h, uintptr npage)
|
|
{
|
|
uintptr reclaimed, n;
|
|
|
|
// First try to sweep busy spans with large objects of size >= npage,
|
|
// this has good chances of reclaiming the necessary space.
|
|
for(n=npage; n < nelem(h->busy); n++) {
|
|
if(MHeap_ReclaimList(h, &h->busy[n], npage))
|
|
return; // Bingo!
|
|
}
|
|
|
|
// Then -- even larger objects.
|
|
if(MHeap_ReclaimList(h, &h->busylarge, npage))
|
|
return; // Bingo!
|
|
|
|
// Now try smaller objects.
|
|
// One such object is not enough, so we need to reclaim several of them.
|
|
reclaimed = 0;
|
|
for(n=0; n < npage && n < nelem(h->busy); n++) {
|
|
reclaimed += MHeap_ReclaimList(h, &h->busy[n], npage-reclaimed);
|
|
if(reclaimed >= npage)
|
|
return;
|
|
}
|
|
|
|
// Now sweep everything that is not yet swept.
|
|
runtime_unlock(h);
|
|
for(;;) {
|
|
n = runtime_sweepone();
|
|
if(n == (uintptr)-1) // all spans are swept
|
|
break;
|
|
reclaimed += n;
|
|
if(reclaimed >= npage)
|
|
break;
|
|
}
|
|
runtime_lock(h);
|
|
}
|
|
|
|
// Allocate a new span of npage pages from the heap
|
|
// and record its size class in the HeapMap and HeapMapCache.
|
|
MSpan*
|
|
runtime_MHeap_Alloc(MHeap *h, uintptr npage, int32 sizeclass, bool large, bool needzero)
|
|
{
|
|
MStats *pmstats;
|
|
MSpan *s;
|
|
|
|
runtime_lock(h);
|
|
pmstats = mstats();
|
|
pmstats->heap_alloc += (intptr)runtime_m()->mcache->local_cachealloc;
|
|
runtime_m()->mcache->local_cachealloc = 0;
|
|
s = MHeap_AllocLocked(h, npage, sizeclass);
|
|
if(s != nil) {
|
|
pmstats->heap_inuse += npage<<PageShift;
|
|
if(large) {
|
|
pmstats->heap_objects++;
|
|
pmstats->heap_alloc += npage<<PageShift;
|
|
// Swept spans are at the end of lists.
|
|
if(s->npages < nelem(h->free))
|
|
runtime_MSpanList_InsertBack(&h->busy[s->npages], s);
|
|
else
|
|
runtime_MSpanList_InsertBack(&h->busylarge, s);
|
|
}
|
|
}
|
|
runtime_unlock(h);
|
|
if(s != nil) {
|
|
if(needzero && s->needzero)
|
|
runtime_memclr((byte*)(s->start<<PageShift), s->npages<<PageShift);
|
|
s->needzero = 0;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static MSpan*
|
|
MHeap_AllocLocked(MHeap *h, uintptr npage, int32 sizeclass)
|
|
{
|
|
uintptr n;
|
|
MSpan *s, *t;
|
|
PageID p;
|
|
|
|
// To prevent excessive heap growth, before allocating n pages
|
|
// we need to sweep and reclaim at least n pages.
|
|
if(!h->sweepdone)
|
|
MHeap_Reclaim(h, npage);
|
|
|
|
// Try in fixed-size lists up to max.
|
|
for(n=npage; n < nelem(h->free); n++) {
|
|
if(!runtime_MSpanList_IsEmpty(&h->free[n])) {
|
|
s = h->free[n].next;
|
|
goto HaveSpan;
|
|
}
|
|
}
|
|
|
|
// Best fit in list of large spans.
|
|
if((s = MHeap_AllocLarge(h, npage)) == nil) {
|
|
if(!MHeap_Grow(h, npage))
|
|
return nil;
|
|
if((s = MHeap_AllocLarge(h, npage)) == nil)
|
|
return nil;
|
|
}
|
|
|
|
HaveSpan:
|
|
// Mark span in use.
|
|
if(s->state != MSpanFree)
|
|
runtime_throw("MHeap_AllocLocked - MSpan not free");
|
|
if(s->npages < npage)
|
|
runtime_throw("MHeap_AllocLocked - bad npages");
|
|
runtime_MSpanList_Remove(s);
|
|
runtime_atomicstore(&s->sweepgen, h->sweepgen);
|
|
s->state = MSpanInUse;
|
|
mstats()->heap_idle -= s->npages<<PageShift;
|
|
mstats()->heap_released -= s->npreleased<<PageShift;
|
|
if(s->npreleased > 0)
|
|
runtime_SysUsed((void*)(s->start<<PageShift), s->npages<<PageShift);
|
|
s->npreleased = 0;
|
|
|
|
if(s->npages > npage) {
|
|
// Trim extra and put it back in the heap.
|
|
t = runtime_FixAlloc_Alloc(&h->spanalloc);
|
|
runtime_MSpan_Init(t, s->start + npage, s->npages - npage);
|
|
s->npages = npage;
|
|
p = t->start;
|
|
p -= ((uintptr)h->arena_start>>PageShift);
|
|
if(p > 0)
|
|
h->spans[p-1] = s;
|
|
h->spans[p] = t;
|
|
h->spans[p+t->npages-1] = t;
|
|
t->needzero = s->needzero;
|
|
runtime_atomicstore(&t->sweepgen, h->sweepgen);
|
|
t->state = MSpanInUse;
|
|
MHeap_FreeLocked(h, t);
|
|
t->unusedsince = s->unusedsince; // preserve age
|
|
}
|
|
s->unusedsince = 0;
|
|
|
|
// Record span info, because gc needs to be
|
|
// able to map interior pointer to containing span.
|
|
s->sizeclass = sizeclass;
|
|
s->elemsize = (sizeclass==0 ? s->npages<<PageShift : (uintptr)runtime_class_to_size[sizeclass]);
|
|
s->types.compression = MTypes_Empty;
|
|
p = s->start;
|
|
p -= ((uintptr)h->arena_start>>PageShift);
|
|
for(n=0; n<npage; n++)
|
|
h->spans[p+n] = s;
|
|
return s;
|
|
}
|
|
|
|
// Allocate a span of exactly npage pages from the list of large spans.
|
|
static MSpan*
|
|
MHeap_AllocLarge(MHeap *h, uintptr npage)
|
|
{
|
|
return BestFit(&h->freelarge, npage, nil);
|
|
}
|
|
|
|
// Search list for smallest span with >= npage pages.
|
|
// If there are multiple smallest spans, take the one
|
|
// with the earliest starting address.
|
|
static MSpan*
|
|
BestFit(MSpan *list, uintptr npage, MSpan *best)
|
|
{
|
|
MSpan *s;
|
|
|
|
for(s=list->next; s != list; s=s->next) {
|
|
if(s->npages < npage)
|
|
continue;
|
|
if(best == nil
|
|
|| s->npages < best->npages
|
|
|| (s->npages == best->npages && s->start < best->start))
|
|
best = s;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
// Try to add at least npage pages of memory to the heap,
|
|
// returning whether it worked.
|
|
static bool
|
|
MHeap_Grow(MHeap *h, uintptr npage)
|
|
{
|
|
uintptr ask;
|
|
void *v;
|
|
MSpan *s;
|
|
PageID p;
|
|
|
|
// Ask for a big chunk, to reduce the number of mappings
|
|
// the operating system needs to track; also amortizes
|
|
// the overhead of an operating system mapping.
|
|
// Allocate a multiple of 64kB (16 pages).
|
|
npage = (npage+15)&~15;
|
|
ask = npage<<PageShift;
|
|
if(ask < HeapAllocChunk)
|
|
ask = HeapAllocChunk;
|
|
|
|
v = runtime_MHeap_SysAlloc(h, ask);
|
|
if(v == nil) {
|
|
if(ask > (npage<<PageShift)) {
|
|
ask = npage<<PageShift;
|
|
v = runtime_MHeap_SysAlloc(h, ask);
|
|
}
|
|
if(v == nil) {
|
|
runtime_printf("runtime: out of memory: cannot allocate %D-byte block (%D in use)\n", (uint64)ask, mstats()->heap_sys);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Create a fake "in use" span and free it, so that the
|
|
// right coalescing happens.
|
|
s = runtime_FixAlloc_Alloc(&h->spanalloc);
|
|
runtime_MSpan_Init(s, (uintptr)v>>PageShift, ask>>PageShift);
|
|
p = s->start;
|
|
p -= ((uintptr)h->arena_start>>PageShift);
|
|
h->spans[p] = s;
|
|
h->spans[p + s->npages - 1] = s;
|
|
runtime_atomicstore(&s->sweepgen, h->sweepgen);
|
|
s->state = MSpanInUse;
|
|
MHeap_FreeLocked(h, s);
|
|
return true;
|
|
}
|
|
|
|
// Look up the span at the given address.
|
|
// Address is guaranteed to be in map
|
|
// and is guaranteed to be start or end of span.
|
|
MSpan*
|
|
runtime_MHeap_Lookup(MHeap *h, void *v)
|
|
{
|
|
uintptr p;
|
|
|
|
p = (uintptr)v;
|
|
p -= (uintptr)h->arena_start;
|
|
return h->spans[p >> PageShift];
|
|
}
|
|
|
|
// Look up the span at the given address.
|
|
// Address is *not* guaranteed to be in map
|
|
// and may be anywhere in the span.
|
|
// Map entries for the middle of a span are only
|
|
// valid for allocated spans. Free spans may have
|
|
// other garbage in their middles, so we have to
|
|
// check for that.
|
|
MSpan*
|
|
runtime_MHeap_LookupMaybe(MHeap *h, void *v)
|
|
{
|
|
MSpan *s;
|
|
PageID p, q;
|
|
|
|
if((byte*)v < h->arena_start || (byte*)v >= h->arena_used)
|
|
return nil;
|
|
p = (uintptr)v>>PageShift;
|
|
q = p;
|
|
q -= (uintptr)h->arena_start >> PageShift;
|
|
s = h->spans[q];
|
|
if(s == nil || p < s->start || (uintptr)v >= s->limit || s->state != MSpanInUse)
|
|
return nil;
|
|
return s;
|
|
}
|
|
|
|
// Free the span back into the heap.
|
|
void
|
|
runtime_MHeap_Free(MHeap *h, MSpan *s, int32 acct)
|
|
{
|
|
MStats *pmstats;
|
|
|
|
runtime_lock(h);
|
|
pmstats = mstats();
|
|
pmstats->heap_alloc += (intptr)runtime_m()->mcache->local_cachealloc;
|
|
runtime_m()->mcache->local_cachealloc = 0;
|
|
pmstats->heap_inuse -= s->npages<<PageShift;
|
|
if(acct) {
|
|
pmstats->heap_alloc -= s->npages<<PageShift;
|
|
pmstats->heap_objects--;
|
|
}
|
|
MHeap_FreeLocked(h, s);
|
|
runtime_unlock(h);
|
|
}
|
|
|
|
static void
|
|
MHeap_FreeLocked(MHeap *h, MSpan *s)
|
|
{
|
|
MSpan *t;
|
|
PageID p;
|
|
|
|
s->types.compression = MTypes_Empty;
|
|
|
|
if(s->state != MSpanInUse || s->ref != 0 || s->sweepgen != h->sweepgen) {
|
|
runtime_printf("MHeap_FreeLocked - span %p ptr %p state %d ref %d sweepgen %d/%d\n",
|
|
s, s->start<<PageShift, s->state, s->ref, s->sweepgen, h->sweepgen);
|
|
runtime_throw("MHeap_FreeLocked - invalid free");
|
|
}
|
|
mstats()->heap_idle += s->npages<<PageShift;
|
|
s->state = MSpanFree;
|
|
runtime_MSpanList_Remove(s);
|
|
// Stamp newly unused spans. The scavenger will use that
|
|
// info to potentially give back some pages to the OS.
|
|
s->unusedsince = runtime_nanotime();
|
|
s->npreleased = 0;
|
|
|
|
// Coalesce with earlier, later spans.
|
|
p = s->start;
|
|
p -= (uintptr)h->arena_start >> PageShift;
|
|
if(p > 0 && (t = h->spans[p-1]) != nil && t->state != MSpanInUse) {
|
|
s->start = t->start;
|
|
s->npages += t->npages;
|
|
s->npreleased = t->npreleased; // absorb released pages
|
|
s->needzero |= t->needzero;
|
|
p -= t->npages;
|
|
h->spans[p] = s;
|
|
runtime_MSpanList_Remove(t);
|
|
t->state = MSpanDead;
|
|
runtime_FixAlloc_Free(&h->spanalloc, t);
|
|
}
|
|
if((p+s->npages)*sizeof(h->spans[0]) < h->spans_mapped && (t = h->spans[p+s->npages]) != nil && t->state != MSpanInUse) {
|
|
s->npages += t->npages;
|
|
s->npreleased += t->npreleased;
|
|
s->needzero |= t->needzero;
|
|
h->spans[p + s->npages - 1] = s;
|
|
runtime_MSpanList_Remove(t);
|
|
t->state = MSpanDead;
|
|
runtime_FixAlloc_Free(&h->spanalloc, t);
|
|
}
|
|
|
|
// Insert s into appropriate list.
|
|
if(s->npages < nelem(h->free))
|
|
runtime_MSpanList_Insert(&h->free[s->npages], s);
|
|
else
|
|
runtime_MSpanList_Insert(&h->freelarge, s);
|
|
}
|
|
|
|
static void
|
|
forcegchelper(void *vnote)
|
|
{
|
|
Note *note = (Note*)vnote;
|
|
|
|
runtime_gc(1);
|
|
runtime_notewakeup(note);
|
|
}
|
|
|
|
static uintptr
|
|
scavengelist(MSpan *list, uint64 now, uint64 limit)
|
|
{
|
|
uintptr released, sumreleased, start, end, pagesize;
|
|
MSpan *s;
|
|
|
|
if(runtime_MSpanList_IsEmpty(list))
|
|
return 0;
|
|
|
|
sumreleased = 0;
|
|
for(s=list->next; s != list; s=s->next) {
|
|
if((now - s->unusedsince) > limit && s->npreleased != s->npages) {
|
|
released = (s->npages - s->npreleased) << PageShift;
|
|
mstats()->heap_released += released;
|
|
sumreleased += released;
|
|
s->npreleased = s->npages;
|
|
|
|
start = s->start << PageShift;
|
|
end = start + (s->npages << PageShift);
|
|
|
|
// Round start up and end down to ensure we
|
|
// are acting on entire pages.
|
|
pagesize = getpagesize();
|
|
start = ROUND(start, pagesize);
|
|
end &= ~(pagesize - 1);
|
|
if(end > start)
|
|
runtime_SysUnused((void*)start, end - start);
|
|
}
|
|
}
|
|
return sumreleased;
|
|
}
|
|
|
|
static void
|
|
scavenge(int32 k, uint64 now, uint64 limit)
|
|
{
|
|
uint32 i;
|
|
uintptr sumreleased;
|
|
MHeap *h;
|
|
|
|
h = &runtime_mheap;
|
|
sumreleased = 0;
|
|
for(i=0; i < nelem(h->free); i++)
|
|
sumreleased += scavengelist(&h->free[i], now, limit);
|
|
sumreleased += scavengelist(&h->freelarge, now, limit);
|
|
|
|
if(runtime_debug.gctrace > 0) {
|
|
if(sumreleased > 0)
|
|
runtime_printf("scvg%d: %D MB released\n", k, (uint64)sumreleased>>20);
|
|
runtime_printf("scvg%d: inuse: %D, idle: %D, sys: %D, released: %D, consumed: %D (MB)\n",
|
|
k, mstats()->heap_inuse>>20, mstats()->heap_idle>>20, mstats()->heap_sys>>20,
|
|
mstats()->heap_released>>20, (mstats()->heap_sys - mstats()->heap_released)>>20);
|
|
}
|
|
}
|
|
|
|
// Release (part of) unused memory to OS.
|
|
// Goroutine created at startup.
|
|
// Loop forever.
|
|
void
|
|
runtime_MHeap_Scavenger(void* dummy)
|
|
{
|
|
G *g;
|
|
MHeap *h;
|
|
uint64 tick, now, forcegc, limit;
|
|
int64 unixnow;
|
|
uint32 k;
|
|
Note note, *notep;
|
|
|
|
USED(dummy);
|
|
|
|
g = runtime_g();
|
|
g->issystem = true;
|
|
g->isbackground = true;
|
|
|
|
// If we go two minutes without a garbage collection, force one to run.
|
|
forcegc = 2*60*1e9;
|
|
// If a span goes unused for 5 minutes after a garbage collection,
|
|
// we hand it back to the operating system.
|
|
limit = 5*60*1e9;
|
|
// Make wake-up period small enough for the sampling to be correct.
|
|
if(forcegc < limit)
|
|
tick = forcegc/2;
|
|
else
|
|
tick = limit/2;
|
|
|
|
h = &runtime_mheap;
|
|
for(k=0;; k++) {
|
|
runtime_noteclear(¬e);
|
|
runtime_notetsleepg(¬e, tick);
|
|
|
|
runtime_lock(h);
|
|
unixnow = runtime_unixnanotime();
|
|
if(unixnow - mstats()->last_gc > forcegc) {
|
|
runtime_unlock(h);
|
|
// The scavenger can not block other goroutines,
|
|
// otherwise deadlock detector can fire spuriously.
|
|
// GC blocks other goroutines via the runtime_worldsema.
|
|
runtime_noteclear(¬e);
|
|
notep = ¬e;
|
|
__go_go(forcegchelper, (void*)notep);
|
|
runtime_notetsleepg(¬e, -1);
|
|
if(runtime_debug.gctrace > 0)
|
|
runtime_printf("scvg%d: GC forced\n", k);
|
|
runtime_lock(h);
|
|
}
|
|
now = runtime_nanotime();
|
|
scavenge(k, now, limit);
|
|
runtime_unlock(h);
|
|
}
|
|
}
|
|
|
|
void runtime_debug_freeOSMemory(void) __asm__("runtime_debug.freeOSMemory");
|
|
|
|
void
|
|
runtime_debug_freeOSMemory(void)
|
|
{
|
|
runtime_gc(2); // force GC and do eager sweep
|
|
runtime_lock(&runtime_mheap);
|
|
scavenge(-1, ~(uintptr)0, 0);
|
|
runtime_unlock(&runtime_mheap);
|
|
}
|
|
|
|
// Initialize a new span with the given start and npages.
|
|
void
|
|
runtime_MSpan_Init(MSpan *span, PageID start, uintptr npages)
|
|
{
|
|
span->next = nil;
|
|
span->prev = nil;
|
|
span->start = start;
|
|
span->npages = npages;
|
|
span->freelist = nil;
|
|
span->ref = 0;
|
|
span->sizeclass = 0;
|
|
span->incache = false;
|
|
span->elemsize = 0;
|
|
span->state = MSpanDead;
|
|
span->unusedsince = 0;
|
|
span->npreleased = 0;
|
|
span->types.compression = MTypes_Empty;
|
|
span->speciallock.key = 0;
|
|
span->specials = nil;
|
|
span->needzero = 0;
|
|
span->freebuf = nil;
|
|
}
|
|
|
|
// Initialize an empty doubly-linked list.
|
|
void
|
|
runtime_MSpanList_Init(MSpan *list)
|
|
{
|
|
list->state = MSpanListHead;
|
|
list->next = list;
|
|
list->prev = list;
|
|
}
|
|
|
|
void
|
|
runtime_MSpanList_Remove(MSpan *span)
|
|
{
|
|
if(span->prev == nil && span->next == nil)
|
|
return;
|
|
span->prev->next = span->next;
|
|
span->next->prev = span->prev;
|
|
span->prev = nil;
|
|
span->next = nil;
|
|
}
|
|
|
|
bool
|
|
runtime_MSpanList_IsEmpty(MSpan *list)
|
|
{
|
|
return list->next == list;
|
|
}
|
|
|
|
void
|
|
runtime_MSpanList_Insert(MSpan *list, MSpan *span)
|
|
{
|
|
if(span->next != nil || span->prev != nil) {
|
|
runtime_printf("failed MSpanList_Insert %p %p %p\n", span, span->next, span->prev);
|
|
runtime_throw("MSpanList_Insert");
|
|
}
|
|
span->next = list->next;
|
|
span->prev = list;
|
|
span->next->prev = span;
|
|
span->prev->next = span;
|
|
}
|
|
|
|
void
|
|
runtime_MSpanList_InsertBack(MSpan *list, MSpan *span)
|
|
{
|
|
if(span->next != nil || span->prev != nil) {
|
|
runtime_printf("failed MSpanList_Insert %p %p %p\n", span, span->next, span->prev);
|
|
runtime_throw("MSpanList_Insert");
|
|
}
|
|
span->next = list;
|
|
span->prev = list->prev;
|
|
span->next->prev = span;
|
|
span->prev->next = span;
|
|
}
|
|
|
|
// Adds the special record s to the list of special records for
|
|
// the object p. All fields of s should be filled in except for
|
|
// offset & next, which this routine will fill in.
|
|
// Returns true if the special was successfully added, false otherwise.
|
|
// (The add will fail only if a record with the same p and s->kind
|
|
// already exists.)
|
|
static bool
|
|
addspecial(void *p, Special *s)
|
|
{
|
|
MSpan *span;
|
|
Special **t, *x;
|
|
uintptr offset;
|
|
byte kind;
|
|
|
|
span = runtime_MHeap_LookupMaybe(&runtime_mheap, p);
|
|
if(span == nil)
|
|
runtime_throw("addspecial on invalid pointer");
|
|
|
|
// Ensure that the span is swept.
|
|
// GC accesses specials list w/o locks. And it's just much safer.
|
|
runtime_m()->locks++;
|
|
runtime_MSpan_EnsureSwept(span);
|
|
|
|
offset = (uintptr)p - (span->start << PageShift);
|
|
kind = s->kind;
|
|
|
|
runtime_lock(&span->speciallock);
|
|
|
|
// Find splice point, check for existing record.
|
|
t = &span->specials;
|
|
while((x = *t) != nil) {
|
|
if(offset == x->offset && kind == x->kind) {
|
|
runtime_unlock(&span->speciallock);
|
|
runtime_m()->locks--;
|
|
return false; // already exists
|
|
}
|
|
if(offset < x->offset || (offset == x->offset && kind < x->kind))
|
|
break;
|
|
t = &x->next;
|
|
}
|
|
// Splice in record, fill in offset.
|
|
s->offset = offset;
|
|
s->next = x;
|
|
*t = s;
|
|
runtime_unlock(&span->speciallock);
|
|
runtime_m()->locks--;
|
|
return true;
|
|
}
|
|
|
|
// Removes the Special record of the given kind for the object p.
|
|
// Returns the record if the record existed, nil otherwise.
|
|
// The caller must FixAlloc_Free the result.
|
|
static Special*
|
|
removespecial(void *p, byte kind)
|
|
{
|
|
MSpan *span;
|
|
Special *s, **t;
|
|
uintptr offset;
|
|
|
|
span = runtime_MHeap_LookupMaybe(&runtime_mheap, p);
|
|
if(span == nil)
|
|
runtime_throw("removespecial on invalid pointer");
|
|
|
|
// Ensure that the span is swept.
|
|
// GC accesses specials list w/o locks. And it's just much safer.
|
|
runtime_m()->locks++;
|
|
runtime_MSpan_EnsureSwept(span);
|
|
|
|
offset = (uintptr)p - (span->start << PageShift);
|
|
|
|
runtime_lock(&span->speciallock);
|
|
t = &span->specials;
|
|
while((s = *t) != nil) {
|
|
// This function is used for finalizers only, so we don't check for
|
|
// "interior" specials (p must be exactly equal to s->offset).
|
|
if(offset == s->offset && kind == s->kind) {
|
|
*t = s->next;
|
|
runtime_unlock(&span->speciallock);
|
|
runtime_m()->locks--;
|
|
return s;
|
|
}
|
|
t = &s->next;
|
|
}
|
|
runtime_unlock(&span->speciallock);
|
|
runtime_m()->locks--;
|
|
return nil;
|
|
}
|
|
|
|
// Adds a finalizer to the object p. Returns true if it succeeded.
|
|
bool
|
|
runtime_addfinalizer(void *p, FuncVal *f, const FuncType *ft, const PtrType *ot)
|
|
{
|
|
SpecialFinalizer *s;
|
|
|
|
runtime_lock(&runtime_mheap.speciallock);
|
|
s = runtime_FixAlloc_Alloc(&runtime_mheap.specialfinalizeralloc);
|
|
runtime_unlock(&runtime_mheap.speciallock);
|
|
s->kind = KindSpecialFinalizer;
|
|
s->fn = f;
|
|
s->ft = ft;
|
|
s->ot = ot;
|
|
if(addspecial(p, s))
|
|
return true;
|
|
|
|
// There was an old finalizer
|
|
runtime_lock(&runtime_mheap.speciallock);
|
|
runtime_FixAlloc_Free(&runtime_mheap.specialfinalizeralloc, s);
|
|
runtime_unlock(&runtime_mheap.speciallock);
|
|
return false;
|
|
}
|
|
|
|
// Removes the finalizer (if any) from the object p.
|
|
void
|
|
runtime_removefinalizer(void *p)
|
|
{
|
|
SpecialFinalizer *s;
|
|
|
|
s = (SpecialFinalizer*)removespecial(p, KindSpecialFinalizer);
|
|
if(s == nil)
|
|
return; // there wasn't a finalizer to remove
|
|
runtime_lock(&runtime_mheap.speciallock);
|
|
runtime_FixAlloc_Free(&runtime_mheap.specialfinalizeralloc, s);
|
|
runtime_unlock(&runtime_mheap.speciallock);
|
|
}
|
|
|
|
// Set the heap profile bucket associated with addr to b.
|
|
void
|
|
runtime_setprofilebucket(void *p, Bucket *b)
|
|
{
|
|
SpecialProfile *s;
|
|
|
|
runtime_lock(&runtime_mheap.speciallock);
|
|
s = runtime_FixAlloc_Alloc(&runtime_mheap.specialprofilealloc);
|
|
runtime_unlock(&runtime_mheap.speciallock);
|
|
s->kind = KindSpecialProfile;
|
|
s->b = b;
|
|
if(!addspecial(p, s))
|
|
runtime_throw("setprofilebucket: profile already set");
|
|
}
|
|
|
|
// Do whatever cleanup needs to be done to deallocate s. It has
|
|
// already been unlinked from the MSpan specials list.
|
|
// Returns true if we should keep working on deallocating p.
|
|
bool
|
|
runtime_freespecial(Special *s, void *p, uintptr size, bool freed)
|
|
{
|
|
SpecialFinalizer *sf;
|
|
SpecialProfile *sp;
|
|
|
|
switch(s->kind) {
|
|
case KindSpecialFinalizer:
|
|
sf = (SpecialFinalizer*)s;
|
|
runtime_queuefinalizer(p, sf->fn, sf->ft, sf->ot);
|
|
runtime_lock(&runtime_mheap.speciallock);
|
|
runtime_FixAlloc_Free(&runtime_mheap.specialfinalizeralloc, sf);
|
|
runtime_unlock(&runtime_mheap.speciallock);
|
|
return false; // don't free p until finalizer is done
|
|
case KindSpecialProfile:
|
|
sp = (SpecialProfile*)s;
|
|
runtime_MProf_Free(sp->b, size, freed);
|
|
runtime_lock(&runtime_mheap.speciallock);
|
|
runtime_FixAlloc_Free(&runtime_mheap.specialprofilealloc, sp);
|
|
runtime_unlock(&runtime_mheap.speciallock);
|
|
return true;
|
|
default:
|
|
runtime_throw("bad special kind");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Free all special records for p.
|
|
void
|
|
runtime_freeallspecials(MSpan *span, void *p, uintptr size)
|
|
{
|
|
Special *s, **t, *list;
|
|
uintptr offset;
|
|
|
|
if(span->sweepgen != runtime_mheap.sweepgen)
|
|
runtime_throw("runtime: freeallspecials: unswept span");
|
|
// first, collect all specials into the list; then, free them
|
|
// this is required to not cause deadlock between span->specialLock and proflock
|
|
list = nil;
|
|
offset = (uintptr)p - (span->start << PageShift);
|
|
runtime_lock(&span->speciallock);
|
|
t = &span->specials;
|
|
while((s = *t) != nil) {
|
|
if(offset + size <= s->offset)
|
|
break;
|
|
if(offset <= s->offset) {
|
|
*t = s->next;
|
|
s->next = list;
|
|
list = s;
|
|
} else
|
|
t = &s->next;
|
|
}
|
|
runtime_unlock(&span->speciallock);
|
|
|
|
while(list != nil) {
|
|
s = list;
|
|
list = s->next;
|
|
if(!runtime_freespecial(s, p, size, true))
|
|
runtime_throw("can't explicitly free an object with a finalizer");
|
|
}
|
|
}
|
|
|
|
// Split an allocated span into two equal parts.
|
|
void
|
|
runtime_MHeap_SplitSpan(MHeap *h, MSpan *s)
|
|
{
|
|
MSpan *t;
|
|
MCentral *c;
|
|
uintptr i;
|
|
uintptr npages;
|
|
PageID p;
|
|
|
|
if(s->state != MSpanInUse)
|
|
runtime_throw("MHeap_SplitSpan on a free span");
|
|
if(s->sizeclass != 0 && s->ref != 1)
|
|
runtime_throw("MHeap_SplitSpan doesn't have an allocated object");
|
|
npages = s->npages;
|
|
|
|
// remove the span from whatever list it is in now
|
|
if(s->sizeclass > 0) {
|
|
// must be in h->central[x].mempty
|
|
c = &h->central[s->sizeclass];
|
|
runtime_lock(c);
|
|
runtime_MSpanList_Remove(s);
|
|
runtime_unlock(c);
|
|
runtime_lock(h);
|
|
} else {
|
|
// must be in h->busy/busylarge
|
|
runtime_lock(h);
|
|
runtime_MSpanList_Remove(s);
|
|
}
|
|
// heap is locked now
|
|
|
|
if(npages == 1) {
|
|
// convert span of 1 PageSize object to a span of 2 PageSize/2 objects.
|
|
s->ref = 2;
|
|
s->sizeclass = runtime_SizeToClass(PageSize/2);
|
|
s->elemsize = PageSize/2;
|
|
} else {
|
|
// convert span of n>1 pages into two spans of n/2 pages each.
|
|
if((s->npages & 1) != 0)
|
|
runtime_throw("MHeap_SplitSpan on an odd size span");
|
|
|
|
// compute position in h->spans
|
|
p = s->start;
|
|
p -= (uintptr)h->arena_start >> PageShift;
|
|
|
|
// Allocate a new span for the first half.
|
|
t = runtime_FixAlloc_Alloc(&h->spanalloc);
|
|
runtime_MSpan_Init(t, s->start, npages/2);
|
|
t->limit = (uintptr)((t->start + npages/2) << PageShift);
|
|
t->state = MSpanInUse;
|
|
t->elemsize = npages << (PageShift - 1);
|
|
t->sweepgen = s->sweepgen;
|
|
if(t->elemsize <= MaxSmallSize) {
|
|
t->sizeclass = runtime_SizeToClass(t->elemsize);
|
|
t->ref = 1;
|
|
}
|
|
|
|
// the old span holds the second half.
|
|
s->start += npages/2;
|
|
s->npages = npages/2;
|
|
s->elemsize = npages << (PageShift - 1);
|
|
if(s->elemsize <= MaxSmallSize) {
|
|
s->sizeclass = runtime_SizeToClass(s->elemsize);
|
|
s->ref = 1;
|
|
}
|
|
|
|
// update span lookup table
|
|
for(i = p; i < p + npages/2; i++)
|
|
h->spans[i] = t;
|
|
}
|
|
|
|
// place the span into a new list
|
|
if(s->sizeclass > 0) {
|
|
runtime_unlock(h);
|
|
c = &h->central[s->sizeclass];
|
|
runtime_lock(c);
|
|
// swept spans are at the end of the list
|
|
runtime_MSpanList_InsertBack(&c->mempty, s);
|
|
runtime_unlock(c);
|
|
} else {
|
|
// Swept spans are at the end of lists.
|
|
if(s->npages < nelem(h->free))
|
|
runtime_MSpanList_InsertBack(&h->busy[s->npages], s);
|
|
else
|
|
runtime_MSpanList_InsertBack(&h->busylarge, s);
|
|
runtime_unlock(h);
|
|
}
|
|
}
|