Commit 1665b006 authored by Keith Randall's avatar Keith Randall

runtime: grow stack by copying

On stack overflow, if all frames on the stack are
copyable, we copy the frames to a new stack twice
as large as the old one.  During GC, if a G is using
less than 1/4 of its stack, copy the stack to a stack
half its size.

TODO
- Do something about C frames.  When a C frame is in the
  stack segment, it isn't copyable.  We allocate a new segment
  in this case.
  - For idempotent C code, we can abort it, copy the stack,
    then retry.  I'm working on a separate CL for this.
  - For other C code, we can raise the stackguard
    to the lowest Go frame so the next call that Go frame
    makes triggers a copy, which will then succeed.
- Pick a starting stack size?

The plan is that eventually we reach a point where the
stack contains only copyable frames.

LGTM=rsc
R=dvyukov, rsc
CC=golang-codereviews
https://golang.org/cl/54650044
parent e5f01aee
...@@ -175,6 +175,9 @@ struct MLink ...@@ -175,6 +175,9 @@ struct MLink
// location if that one is unavailable. // location if that one is unavailable.
// //
// SysMap maps previously reserved address space for use. // SysMap maps previously reserved address space for use.
//
// SysFault marks a (already SysAlloc'd) region to fault
// if accessed. Used only for debugging the runtime.
void* runtime·SysAlloc(uintptr nbytes, uint64 *stat); void* runtime·SysAlloc(uintptr nbytes, uint64 *stat);
void runtime·SysFree(void *v, uintptr nbytes, uint64 *stat); void runtime·SysFree(void *v, uintptr nbytes, uint64 *stat);
...@@ -182,6 +185,7 @@ void runtime·SysUnused(void *v, uintptr nbytes); ...@@ -182,6 +185,7 @@ void runtime·SysUnused(void *v, uintptr nbytes);
void runtime·SysUsed(void *v, uintptr nbytes); void runtime·SysUsed(void *v, uintptr nbytes);
void runtime·SysMap(void *v, uintptr nbytes, uint64 *stat); void runtime·SysMap(void *v, uintptr nbytes, uint64 *stat);
void* runtime·SysReserve(void *v, uintptr nbytes); void* runtime·SysReserve(void *v, uintptr nbytes);
void runtime·SysFault(void *v, uintptr nbytes);
// FixAlloc is a simple free-list allocator for fixed size objects. // FixAlloc is a simple free-list allocator for fixed size objects.
// Malloc uses a FixAlloc wrapped around SysAlloc to manages its // Malloc uses a FixAlloc wrapped around SysAlloc to manages its
...@@ -572,6 +576,31 @@ enum ...@@ -572,6 +576,31 @@ enum
DebugTypeAtBlockEnd = 0, DebugTypeAtBlockEnd = 0,
}; };
// Information from the compiler about the layout of stack frames.
typedef struct BitVector BitVector;
struct BitVector
{
int32 n; // # of bits
uint32 data[];
};
typedef struct StackMap StackMap;
struct StackMap
{
int32 n;
uint32 data[];
};
enum {
// Pointer map
BitsPerPointer = 2,
BitsNoPointer = 0,
BitsPointer = 1,
BitsIface = 2,
BitsEface = 3,
};
// Returns pointer map data for the given stackmap index
// (the index is encoded in PCDATA_StackMapIndex).
BitVector* runtime·stackmapdata(StackMap *stackmap, int32 n);
// defined in mgc0.go // defined in mgc0.go
void runtime·gc_m_ptr(Eface*); void runtime·gc_m_ptr(Eface*);
void runtime·gc_itab_ptr(Eface*); void runtime·gc_itab_ptr(Eface*);
......
...@@ -41,6 +41,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat) ...@@ -41,6 +41,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat)
runtime·munmap(v, n); runtime·munmap(v, n);
} }
void
runtime·SysFault(void *v, uintptr n)
{
runtime·mmap(v, n, PROT_NONE, 0, -1, 0);
}
void* void*
runtime·SysReserve(void *v, uintptr n) runtime·SysReserve(void *v, uintptr n)
{ {
......
...@@ -45,6 +45,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat) ...@@ -45,6 +45,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat)
runtime·munmap(v, n); runtime·munmap(v, n);
} }
void
runtime·SysFault(void *v, uintptr n)
{
runtime·mmap(v, n, PROT_NONE, 0, -1, 0);
}
void* void*
runtime·SysReserve(void *v, uintptr n) runtime·SysReserve(void *v, uintptr n)
{ {
......
...@@ -45,6 +45,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat) ...@@ -45,6 +45,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat)
runtime·munmap(v, n); runtime·munmap(v, n);
} }
void
runtime·SysFault(void *v, uintptr n)
{
runtime·mmap(v, n, PROT_NONE, 0, -1, 0);
}
void* void*
runtime·SysReserve(void *v, uintptr n) runtime·SysReserve(void *v, uintptr n)
{ {
......
...@@ -92,6 +92,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat) ...@@ -92,6 +92,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat)
runtime·munmap(v, n); runtime·munmap(v, n);
} }
void
runtime·SysFault(void *v, uintptr n)
{
runtime·mmap(v, n, PROT_NONE, 0, -1, 0);
}
void* void*
runtime·SysReserve(void *v, uintptr n) runtime·SysReserve(void *v, uintptr n)
{ {
......
...@@ -45,6 +45,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat) ...@@ -45,6 +45,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat)
runtime·munmap(v, n); runtime·munmap(v, n);
} }
void
runtime·SysFault(void *v, uintptr n)
{
runtime·mmap(v, n, PROT_NONE, 0, -1, 0);
}
void* void*
runtime·SysReserve(void *v, uintptr n) runtime·SysReserve(void *v, uintptr n)
{ {
......
...@@ -45,6 +45,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat) ...@@ -45,6 +45,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat)
runtime·munmap(v, n); runtime·munmap(v, n);
} }
void
runtime·SysFault(void *v, uintptr n)
{
runtime·mmap(v, n, PROT_NONE, 0, -1, 0);
}
void* void*
runtime·SysReserve(void *v, uintptr n) runtime·SysReserve(void *v, uintptr n)
{ {
......
...@@ -67,6 +67,12 @@ runtime·SysMap(void *v, uintptr nbytes, uint64 *stat) ...@@ -67,6 +67,12 @@ runtime·SysMap(void *v, uintptr nbytes, uint64 *stat)
USED(v, nbytes, stat); USED(v, nbytes, stat);
} }
void
runtime·SysFault(void *v, uintptr n)
{
USED(v, nbytes);
}
void* void*
runtime·SysReserve(void *v, uintptr nbytes) runtime·SysReserve(void *v, uintptr nbytes)
{ {
......
...@@ -46,6 +46,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat) ...@@ -46,6 +46,12 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat)
runtime·munmap(v, n); runtime·munmap(v, n);
} }
void
runtime·SysFault(void *v, uintptr n)
{
runtime·mmap(v, n, PROT_NONE, 0, -1, 0);
}
void* void*
runtime·SysReserve(void *v, uintptr n) runtime·SysReserve(void *v, uintptr n)
{ {
......
...@@ -15,12 +15,15 @@ enum { ...@@ -15,12 +15,15 @@ enum {
MEM_RELEASE = 0x8000, MEM_RELEASE = 0x8000,
PAGE_READWRITE = 0x0004, PAGE_READWRITE = 0x0004,
PAGE_NOACCESS = 0x0001,
}; };
#pragma dynimport runtime·VirtualAlloc VirtualAlloc "kernel32.dll" #pragma dynimport runtime·VirtualAlloc VirtualAlloc "kernel32.dll"
#pragma dynimport runtime·VirtualFree VirtualFree "kernel32.dll" #pragma dynimport runtime·VirtualFree VirtualFree "kernel32.dll"
#pragma dynimport runtime·VirtualProtect VirtualProtect "kernel32.dll"
extern void *runtime·VirtualAlloc; extern void *runtime·VirtualAlloc;
extern void *runtime·VirtualFree; extern void *runtime·VirtualFree;
extern void *runtime·VirtualProtect;
void* void*
runtime·SysAlloc(uintptr n, uint64 *stat) runtime·SysAlloc(uintptr n, uint64 *stat)
...@@ -60,6 +63,16 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat) ...@@ -60,6 +63,16 @@ runtime·SysFree(void *v, uintptr n, uint64 *stat)
runtime·throw("runtime: failed to release pages"); runtime·throw("runtime: failed to release pages");
} }
void
runtime·SysFault(void *v, uintptr n)
{
uintptr r, old;
r = (uintptr)runtime·stdcall(runtime·VirtualProtect, 4, v, n, (uintptr)PAGE_NOACCESS, &old);
if(r == 0)
runtime·throw("runtime: failed to protect pages");
}
void* void*
runtime·SysReserve(void *v, uintptr n) runtime·SysReserve(void *v, uintptr n)
{ {
......
...@@ -84,13 +84,6 @@ enum { ...@@ -84,13 +84,6 @@ enum {
LOOP = 2, LOOP = 2,
PC_BITS = PRECISE | LOOP, PC_BITS = PRECISE | LOOP,
// Pointer map
BitsPerPointer = 2,
BitsNoPointer = 0,
BitsPointer = 1,
BitsIface = 2,
BitsEface = 3,
RootData = 0, RootData = 0,
RootBss = 1, RootBss = 1,
RootFinalizers = 2, RootFinalizers = 2,
...@@ -265,7 +258,7 @@ static Workbuf* handoff(Workbuf*); ...@@ -265,7 +258,7 @@ static Workbuf* handoff(Workbuf*);
static void gchelperstart(void); static void gchelperstart(void);
static void addfinroots(void *wbufp, void *v); static void addfinroots(void *wbufp, void *v);
static void flushallmcaches(void); static void flushallmcaches(void);
static void scanframe(Stkframe *frame, void *wbufp); static bool scanframe(Stkframe *frame, void *wbufp);
static void addstackroots(G *gp, Workbuf **wbufp); static void addstackroots(G *gp, Workbuf **wbufp);
static FuncVal runfinqv = {runfinq}; static FuncVal runfinqv = {runfinq};
...@@ -1445,22 +1438,8 @@ handoff(Workbuf *b) ...@@ -1445,22 +1438,8 @@ handoff(Workbuf *b)
extern byte pclntab[]; // base for f->ptrsoff extern byte pclntab[]; // base for f->ptrsoff
typedef struct BitVector BitVector; BitVector*
struct BitVector runtime·stackmapdata(StackMap *stackmap, int32 n)
{
int32 n;
uint32 data[];
};
typedef struct StackMap StackMap;
struct StackMap
{
int32 n;
uint32 data[];
};
static BitVector*
stackmapdata(StackMap *stackmap, int32 n)
{ {
BitVector *bv; BitVector *bv;
uint32 *ptr; uint32 *ptr;
...@@ -1531,7 +1510,7 @@ scanbitvector(byte *scanp, BitVector *bv, bool afterprologue, void *wbufp) ...@@ -1531,7 +1510,7 @@ scanbitvector(byte *scanp, BitVector *bv, bool afterprologue, void *wbufp)
} }
// Scan a stack frame: local variables and function arguments/results. // Scan a stack frame: local variables and function arguments/results.
static void static bool
scanframe(Stkframe *frame, void *wbufp) scanframe(Stkframe *frame, void *wbufp)
{ {
Func *f; Func *f;
...@@ -1576,7 +1555,7 @@ scanframe(Stkframe *frame, void *wbufp) ...@@ -1576,7 +1555,7 @@ scanframe(Stkframe *frame, void *wbufp)
pcdata, stackmap->n, runtime·funcname(f), targetpc); pcdata, stackmap->n, runtime·funcname(f), targetpc);
runtime·throw("scanframe: bad symbol table"); runtime·throw("scanframe: bad symbol table");
} }
bv = stackmapdata(stackmap, pcdata); bv = runtime·stackmapdata(stackmap, pcdata);
size = (bv->n * PtrSize) / BitsPerPointer; size = (bv->n * PtrSize) / BitsPerPointer;
scanbitvector(frame->varp - size, bv, afterprologue, wbufp); scanbitvector(frame->varp - size, bv, afterprologue, wbufp);
} }
...@@ -1586,10 +1565,11 @@ scanframe(Stkframe *frame, void *wbufp) ...@@ -1586,10 +1565,11 @@ scanframe(Stkframe *frame, void *wbufp)
// Use pointer information if known. // Use pointer information if known.
stackmap = runtime·funcdata(f, FUNCDATA_ArgsPointerMaps); stackmap = runtime·funcdata(f, FUNCDATA_ArgsPointerMaps);
if(stackmap != nil) { if(stackmap != nil) {
bv = stackmapdata(stackmap, pcdata); bv = runtime·stackmapdata(stackmap, pcdata);
scanbitvector(frame->argp, bv, true, wbufp); scanbitvector(frame->argp, bv, true, wbufp);
} else } else
enqueue1(wbufp, (Obj){frame->argp, frame->arglen, 0}); enqueue1(wbufp, (Obj){frame->argp, frame->arglen, 0});
return true;
} }
static void static void
...@@ -1620,6 +1600,10 @@ addstackroots(G *gp, Workbuf **wbufp) ...@@ -1620,6 +1600,10 @@ addstackroots(G *gp, Workbuf **wbufp)
runtime·throw("can't scan our own stack"); runtime·throw("can't scan our own stack");
if((mp = gp->m) != nil && mp->helpgc) if((mp = gp->m) != nil && mp->helpgc)
runtime·throw("can't scan gchelper stack"); runtime·throw("can't scan gchelper stack");
// Shrink stack if not much of it is being used.
runtime·shrinkstack(gp);
if(gp->syscallstack != (uintptr)nil) { if(gp->syscallstack != (uintptr)nil) {
// Scanning another goroutine that is about to enter or might // Scanning another goroutine that is about to enter or might
// have just exited a system call. It may be executing code such // have just exited a system call. It may be executing code such
......
...@@ -173,6 +173,11 @@ runtime·schedinit(void) ...@@ -173,6 +173,11 @@ runtime·schedinit(void)
runtime·allp = runtime·malloc((MaxGomaxprocs+1)*sizeof(runtime·allp[0])); runtime·allp = runtime·malloc((MaxGomaxprocs+1)*sizeof(runtime·allp[0]));
procresize(procs); procresize(procs);
runtime·copystack = runtime·precisestack;
p = runtime·getenv("GOCOPYSTACK");
if(p != nil && !runtime·strcmp(p, (byte*)"0"))
runtime·copystack = false;
mstats.enablegc = 1; mstats.enablegc = 1;
if(raceenabled) if(raceenabled)
...@@ -187,6 +192,15 @@ static FuncVal scavenger = {runtime·MHeap_Scavenger}; ...@@ -187,6 +192,15 @@ static FuncVal scavenger = {runtime·MHeap_Scavenger};
static FuncVal initDone = { runtime·unlockOSThread }; static FuncVal initDone = { runtime·unlockOSThread };
// The main goroutine. // The main goroutine.
// Note: C frames in general are not copyable during stack growth, for two reasons:
// 1) We don't know where in a frame to find pointers to other stack locations.
// 2) There's no guarantee that globals or heap values do not point into the frame.
//
// The C frame for runtime.main is copyable, because:
// 1) There are no pointers to other stack locations in the frame
// (d.fn points at a global, d.link is nil, d.argp is -1).
// 2) The only pointer into this frame is from the defer chain,
// which is explicitly handled during stack copying.
void void
runtime·main(void) runtime·main(void)
{ {
...@@ -1870,8 +1884,20 @@ allgadd(G *gp) ...@@ -1870,8 +1884,20 @@ allgadd(G *gp)
static void static void
gfput(P *p, G *gp) gfput(P *p, G *gp)
{ {
uintptr stksize;
if(gp->stackguard - StackGuard != gp->stack0) if(gp->stackguard - StackGuard != gp->stack0)
runtime·throw("invalid stack in gfput"); runtime·throw("invalid stack in gfput");
stksize = gp->stackbase + sizeof(Stktop) - gp->stack0;
if(stksize != FixedStack) {
// non-standard stack size - free it.
runtime·stackfree((void*)gp->stack0, stksize);
gp->stacksize = 0;
gp->stack0 = 0;
gp->stackguard = 0;
gp->stackguard0 = 0;
gp->stackbase = 0;
}
gp->schedlink = p->gfree; gp->schedlink = p->gfree;
p->gfree = gp; p->gfree = gp;
p->gfreecnt++; p->gfreecnt++;
...@@ -1894,6 +1920,7 @@ static G* ...@@ -1894,6 +1920,7 @@ static G*
gfget(P *p) gfget(P *p)
{ {
G *gp; G *gp;
byte *stk;
retry: retry:
gp = p->gfree; gp = p->gfree;
...@@ -1912,6 +1939,24 @@ retry: ...@@ -1912,6 +1939,24 @@ retry:
if(gp) { if(gp) {
p->gfree = gp->schedlink; p->gfree = gp->schedlink;
p->gfreecnt--; p->gfreecnt--;
if(gp->stack0 == 0) {
// Stack was deallocated in gfput. Allocate a new one.
if(g == m->g0) {
stk = runtime·stackalloc(FixedStack);
} else {
g->param = (void*)FixedStack;
runtime·mcall(mstackalloc);
stk = g->param;
g->param = nil;
}
gp->stacksize = FixedStack;
gp->stack0 = (uintptr)stk;
gp->stackbase = (uintptr)stk + FixedStack - sizeof(Stktop);
gp->stackguard = (uintptr)stk + StackGuard;
gp->stackguard0 = gp->stackguard;
runtime·memclr((byte*)gp->stackbase, sizeof(Stktop));
}
} }
return gp; return gp;
} }
......
...@@ -589,6 +589,7 @@ struct DebugVars ...@@ -589,6 +589,7 @@ struct DebugVars
}; };
extern bool runtime·precisestack; extern bool runtime·precisestack;
extern bool runtime·copystack;
/* /*
* defined macros * defined macros
...@@ -732,7 +733,7 @@ struct Stkframe ...@@ -732,7 +733,7 @@ struct Stkframe
uintptr arglen; // number of bytes at argp uintptr arglen; // number of bytes at argp
}; };
int32 runtime·gentraceback(uintptr, uintptr, uintptr, G*, int32, uintptr*, int32, void(*)(Stkframe*, void*), void*, bool); int32 runtime·gentraceback(uintptr, uintptr, uintptr, G*, int32, uintptr*, int32, bool(*)(Stkframe*, void*), void*, bool);
void runtime·traceback(uintptr pc, uintptr sp, uintptr lr, G* gp); void runtime·traceback(uintptr pc, uintptr sp, uintptr lr, G* gp);
void runtime·tracebackothers(G*); void runtime·tracebackothers(G*);
bool runtime·haszeroargs(uintptr pc); bool runtime·haszeroargs(uintptr pc);
...@@ -860,6 +861,7 @@ int8* runtime·funcname(Func*); ...@@ -860,6 +861,7 @@ int8* runtime·funcname(Func*);
int32 runtime·pcdatavalue(Func*, int32, uintptr); int32 runtime·pcdatavalue(Func*, int32, uintptr);
void* runtime·stackalloc(uint32); void* runtime·stackalloc(uint32);
void runtime·stackfree(void*, uintptr); void runtime·stackfree(void*, uintptr);
void runtime·shrinkstack(G*);
MCache* runtime·allocmcache(void); MCache* runtime·allocmcache(void);
void runtime·freemcache(MCache*); void runtime·freemcache(MCache*);
void runtime·mallocinit(void); void runtime·mallocinit(void);
......
...@@ -6,10 +6,20 @@ ...@@ -6,10 +6,20 @@
#include "arch_GOARCH.h" #include "arch_GOARCH.h"
#include "malloc.h" #include "malloc.h"
#include "stack.h" #include "stack.h"
#include "funcdata.h"
#include "typekind.h"
#include "type.h"
enum enum
{ {
// StackDebug == 0: no logging
// == 1: logging of per-stack operations
// == 2: logging of per-frame operations
// == 3: logging of per-word updates
// == 4: logging of per-word reads
StackDebug = 0, StackDebug = 0,
StackFromSystem = 0, // allocate stacks from system memory instead of the heap
StackFaultOnFree = 0, // old stacks are mapped noaccess to detect use after free
}; };
typedef struct StackCacheNode StackCacheNode; typedef struct StackCacheNode StackCacheNode;
...@@ -84,12 +94,17 @@ runtime·stackalloc(uint32 n) ...@@ -84,12 +94,17 @@ runtime·stackalloc(uint32 n)
// Doing so would cause a deadlock (issue 1547). // Doing so would cause a deadlock (issue 1547).
if(g != m->g0) if(g != m->g0)
runtime·throw("stackalloc not on scheduler stack"); runtime·throw("stackalloc not on scheduler stack");
if(StackDebug >= 1)
runtime·printf("stackalloc %d\n", n);
// Stacks are usually allocated with a fixed-size free-list allocator, if(StackFromSystem)
// but if we need a stack of non-standard size, we fall back on malloc return runtime·SysAlloc(ROUND(n, PageSize), &mstats.stacks_sys);
// (assuming that inside malloc and GC all the stack frames are small,
// Minimum-sized stacks are allocated with a fixed-size free-list allocator,
// but if we need a stack of a bigger size, we fall back on malloc
// (assuming that inside malloc all the stack frames are small,
// so that we do not deadlock). // so that we do not deadlock).
if(n == FixedStack || m->mallocing || m->gcing) { if(n == FixedStack || m->mallocing) {
if(n != FixedStack) { if(n != FixedStack) {
runtime·printf("stackalloc: in malloc, size=%d want %d\n", FixedStack, n); runtime·printf("stackalloc: in malloc, size=%d want %d\n", FixedStack, n);
runtime·throw("stackalloc"); runtime·throw("stackalloc");
...@@ -112,6 +127,16 @@ runtime·stackfree(void *v, uintptr n) ...@@ -112,6 +127,16 @@ runtime·stackfree(void *v, uintptr n)
{ {
uint32 pos; uint32 pos;
if(StackDebug >= 1)
runtime·printf("stackfree %p %d\n", v, (int32)n);
if(StackFromSystem) {
if(StackFaultOnFree)
runtime·SysFault(v, n);
else
runtime·SysFree(v, n, &mstats.stacks_sys);
return;
}
if(n == FixedStack || m->mallocing || m->gcing) { if(n == FixedStack || m->mallocing || m->gcing) {
if(m->stackcachecnt == StackCacheSize) if(m->stackcachecnt == StackCacheSize)
stackcacherelease(); stackcacherelease();
...@@ -145,7 +170,7 @@ runtime·oldstack(void) ...@@ -145,7 +170,7 @@ runtime·oldstack(void)
sp = (byte*)top; sp = (byte*)top;
argsize = top->argsize; argsize = top->argsize;
if(StackDebug) { if(StackDebug >= 1) {
runtime·printf("runtime: oldstack gobuf={pc:%p sp:%p lr:%p} cret=%p argsize=%p\n", runtime·printf("runtime: oldstack gobuf={pc:%p sp:%p lr:%p} cret=%p argsize=%p\n",
top->gobuf.pc, top->gobuf.sp, top->gobuf.lr, m->cret, (uintptr)argsize); top->gobuf.pc, top->gobuf.sp, top->gobuf.lr, m->cret, (uintptr)argsize);
} }
...@@ -187,6 +212,330 @@ runtime·oldstack(void) ...@@ -187,6 +212,330 @@ runtime·oldstack(void)
uintptr runtime·maxstacksize = 1<<20; // enough until runtime.main sets it for real uintptr runtime·maxstacksize = 1<<20; // enough until runtime.main sets it for real
static uint8*
mapnames[] = {
(uint8*)"---",
(uint8*)"ptr",
(uint8*)"iface",
(uint8*)"eface",
};
// Stack frame layout
//
// (x86)
// +------------------+
// | args from caller |
// +------------------+ <- frame->argp
// | return address |
// +------------------+ <- frame->varp
// | locals |
// +------------------+
// | args to callee |
// +------------------+ <- frame->sp
//
// (arm: TODO)
typedef struct CopyableInfo CopyableInfo;
struct CopyableInfo {
byte *stk; // bottom address of segment
byte *base; // top address of segment (including Stktop)
int32 frames; // count of copyable frames (-1 = not copyable)
};
void runtime·main(void);
static bool
checkframecopy(Stkframe *frame, void *arg)
{
CopyableInfo *cinfo;
Func *f;
StackMap *stackmap;
cinfo = arg;
f = frame->fn;
if(StackDebug >= 2)
runtime·printf(" checking %s frame=[%p,%p] stk=[%p,%p]\n", runtime·funcname(f), frame->sp, frame->fp, cinfo->stk, cinfo->base);
// if we're not in the segment any more, return immediately.
if(frame->varp < cinfo->stk || frame->varp >= cinfo->base) {
if(StackDebug >= 2)
runtime·printf(" <next segment>\n");
return false; // stop traceback
}
if(f->entry == (uintptr)runtime·main) {
// A special routine at the TOS of the main routine.
// We will allow it to be copied even though we don't
// have full GC info for it (because it is written in C).
cinfo->frames++;
return false; // stop traceback
}
if(frame->varp != (byte*)frame->sp) { // not in prologue (and has at least one local or outarg)
stackmap = runtime·funcdata(f, FUNCDATA_LocalsPointerMaps);
if(stackmap == nil) {
cinfo->frames = -1;
if(StackDebug >= 1)
runtime·printf("copystack: no locals info for %s\n", runtime·funcname(f));
return false;
}
if(stackmap->n <= 0) {
cinfo->frames = -1;
if(StackDebug >= 1)
runtime·printf("copystack: locals size info only for %s\n", runtime·funcname(f));
return false;
}
}
if(frame->arglen != 0) {
stackmap = runtime·funcdata(f, FUNCDATA_ArgsPointerMaps);
if(stackmap == nil) {
cinfo->frames = -1;
if(StackDebug >= 1)
runtime·printf("copystack: no arg info for %s\n", runtime·funcname(f));
return false;
}
}
cinfo->frames++;
return true; // this frame is ok; keep going
}
// If the top segment of the stack contains an uncopyable
// frame, return -1. Otherwise return the number of frames
// in the top segment, all of which are copyable.
static int32
copyabletopsegment(G *gp)
{
CopyableInfo cinfo;
cinfo.stk = (byte*)gp->stackguard - StackGuard;
cinfo.base = (byte*)gp->stackbase + sizeof(Stktop);
cinfo.frames = 0;
runtime·gentraceback(~(uintptr)0, ~(uintptr)0, 0, gp, 0, nil, 0x7fffffff, checkframecopy, &cinfo, false);
if(StackDebug >= 1 && cinfo.frames != -1)
runtime·printf("copystack: %d copyable frames\n", cinfo.frames);
return cinfo.frames;
}
typedef struct AdjustInfo AdjustInfo;
struct AdjustInfo {
byte *oldstk; // bottom address of segment
byte *oldbase; // top address of segment (after Stktop)
uintptr delta; // ptr distance from old to new stack (newbase - oldbase)
};
// bv describes the memory starting at address scanp.
// Adjust any pointers contained therein.
static void
adjustpointers(byte **scanp, BitVector *bv, AdjustInfo *adjinfo, Func *f)
{
uintptr delta;
int32 num, i;
byte *p, *minp, *maxp;
Type *t;
Itab *tab;
minp = adjinfo->oldstk;
maxp = adjinfo->oldbase;
delta = adjinfo->delta;
num = bv->n / BitsPerPointer;
for(i = 0; i < num; i++) {
if(StackDebug >= 4)
runtime·printf(" %p:%s:%p\n", &scanp[i], mapnames[bv->data[i / (32 / BitsPerPointer)] >> (i * BitsPerPointer & 31) & 3], scanp[i]);
switch(bv->data[i / (32 / BitsPerPointer)] >> (i * BitsPerPointer & 31) & 3) {
case BitsNoPointer:
break;
case BitsPointer:
p = scanp[i];
if(f != nil && (byte*)0 < p && p < (byte*)PageSize) {
// Looks like a junk value in a pointer slot.
// Live analysis wrong?
runtime·printf("%p: %p %s\n", &scanp[i], p, runtime·funcname(f));
runtime·throw("bad pointer!");
}
if(minp <= p && p < maxp) {
if(StackDebug >= 3)
runtime·printf("adjust ptr %p\n", p);
scanp[i] = p + delta;
}
break;
case BitsEface:
t = (Type*)scanp[i];
if(t != nil && (t->size > PtrSize || (t->kind & KindNoPointers) == 0)) {
p = scanp[i+1];
if(minp <= p && p < maxp) {
if(StackDebug >= 3)
runtime·printf("adjust eface %p\n", p);
if(t->size > PtrSize) // currently we always allocate such objects on the heap
runtime·throw("large interface value found on stack");
scanp[i+1] = p + delta;
}
}
break;
case BitsIface:
tab = (Itab*)scanp[i];
if(tab != nil) {
t = tab->type;
if(t->size > PtrSize || (t->kind & KindNoPointers) == 0) {
p = scanp[i+1];
if(minp <= p && p < maxp) {
if(StackDebug >= 3)
runtime·printf("adjust iface %p\n", p);
if(t->size > PtrSize) // currently we always allocate such objects on the heap
runtime·throw("large interface value found on stack");
scanp[i+1] = p + delta;
}
}
}
break;
}
}
}
// Note: the argument/return area is adjusted by the callee.
static bool
adjustframe(Stkframe *frame, void *arg)
{
AdjustInfo *adjinfo;
Func *f;
StackMap *stackmap;
int32 pcdata;
BitVector *bv;
adjinfo = arg;
f = frame->fn;
if(StackDebug >= 2)
runtime·printf(" adjusting %s frame=[%p,%p]\n", runtime·funcname(f), frame->sp, frame->fp);
if(f->entry == (uintptr)runtime·main)
return true;
pcdata = runtime·pcdatavalue(f, PCDATA_StackMapIndex, frame->pc);
if(pcdata == -1)
pcdata = 0; // in prologue
// adjust local pointers
if(frame->varp != (byte*)frame->sp) {
stackmap = runtime·funcdata(f, FUNCDATA_LocalsPointerMaps);
if(stackmap == nil)
runtime·throw("no locals info");
if(stackmap->n <= 0)
runtime·throw("locals size info only");
bv = runtime·stackmapdata(stackmap, pcdata);
if(StackDebug >= 3)
runtime·printf(" locals\n");
adjustpointers((byte**)frame->varp - bv->n / BitsPerPointer, bv, adjinfo, f);
}
// adjust inargs and outargs
if(frame->arglen != 0) {
stackmap = runtime·funcdata(f, FUNCDATA_ArgsPointerMaps);
if(stackmap == nil)
runtime·throw("no arg info");
bv = runtime·stackmapdata(stackmap, pcdata);
if(StackDebug >= 3)
runtime·printf(" args\n");
adjustpointers((byte**)frame->argp, bv, adjinfo, nil);
}
return true;
}
static void
adjustctxt(G *gp, AdjustInfo *adjinfo)
{
if(adjinfo->oldstk <= (byte*)gp->sched.ctxt && (byte*)gp->sched.ctxt < adjinfo->oldbase)
gp->sched.ctxt = (byte*)gp->sched.ctxt + adjinfo->delta;
}
static void
adjustdefers(G *gp, AdjustInfo *adjinfo)
{
Defer *d, **dp;
Func *f;
FuncVal *fn;
StackMap *stackmap;
BitVector *bv;
for(dp = &gp->defer, d = *dp; d != nil; dp = &d->link, d = *dp) {
if(adjinfo->oldstk <= (byte*)d && (byte*)d < adjinfo->oldbase) {
// The Defer record is on the stack. Its fields will
// get adjusted appropriately.
// This only happens for runtime.main now, but a compiler
// optimization could do more of this.
*dp = (Defer*)((byte*)d + adjinfo->delta);
continue;
}
if(d->argp < adjinfo->oldstk || adjinfo->oldbase <= d->argp)
break; // a defer for the next segment
f = runtime·findfunc((uintptr)d->fn->fn);
if(f == nil) {
runtime·printf("runtime: bad defer %p %d %d %p %p\n", d->fn->fn, d->siz, d->special, d->argp, d->pc);
runtime·printf("caller %s\n", runtime·funcname(runtime·findfunc((uintptr)d->pc)));
runtime·throw("can't adjust unknown defer");
}
if(StackDebug >= 4)
runtime·printf(" checking defer %s\n", runtime·funcname(f));
// Defer's FuncVal might be on the stack
fn = d->fn;
if(adjinfo->oldstk <= (byte*)fn && (byte*)fn < adjinfo->oldbase) {
if(StackDebug >= 3)
runtime·printf(" adjust defer fn %s\n", runtime·funcname(f));
d->fn = (FuncVal*)((byte*)fn + adjinfo->delta);
} else {
// deferred function's closure args might point into the stack.
if(StackDebug >= 3)
runtime·printf(" adjust deferred args for %s\n", runtime·funcname(f));
stackmap = runtime·funcdata(f, FUNCDATA_ArgsPointerMaps);
if(stackmap == nil)
runtime·throw("runtime: deferred function has no arg ptr map");
bv = runtime·stackmapdata(stackmap, 0);
adjustpointers(d->args, bv, adjinfo, f);
}
d->argp += adjinfo->delta;
}
}
// Copies the top stack segment of gp to a new stack segment of a
// different size. The top segment must contain nframes frames.
static void
copystack(G *gp, uintptr nframes, uintptr newsize)
{
byte *oldstk, *oldbase, *newstk, *newbase;
uintptr oldsize, used;
AdjustInfo adjinfo;
if(gp->syscallstack != 0)
runtime·throw("can't handle stack copy in syscall yet");
oldstk = (byte*)gp->stackguard - StackGuard;
oldbase = (byte*)gp->stackbase + sizeof(Stktop);
oldsize = oldbase - oldstk;
used = oldbase - (byte*)gp->sched.sp;
// allocate new stack
newstk = runtime·stackalloc(newsize);
newbase = newstk + newsize;
if(StackDebug >= 1)
runtime·printf("copystack [%p %p]/%d -> [%p %p]/%d\n", oldstk, oldbase, (int32)oldsize, newstk, newbase, (int32)newsize);
// adjust pointers in the to-be-copied frames
adjinfo.oldstk = oldstk;
adjinfo.oldbase = oldbase;
adjinfo.delta = newbase - oldbase;
runtime·gentraceback(~(uintptr)0, ~(uintptr)0, 0, gp, 0, nil, nframes, adjustframe, &adjinfo, false);
// adjust other miscellaneous things that have pointers into stacks.
adjustctxt(gp, &adjinfo);
adjustdefers(gp, &adjinfo);
// copy the stack to the new location
runtime·memmove(newbase - used, oldbase - used, used);
// Swap out old stack for new one
gp->stackbase = (uintptr)newbase - sizeof(Stktop);
gp->stackguard = (uintptr)newstk + StackGuard;
gp->stackguard0 = (uintptr)newstk + StackGuard; // NOTE: might clobber a preempt request
if(gp->stack0 == (uintptr)oldstk)
gp->stack0 = (uintptr)newstk;
gp->sched.sp = (uintptr)(newbase - used);
// free old stack
runtime·stackfree(oldstk, oldsize);
}
// Called from runtime·newstackcall or from runtime·morestack when a new // Called from runtime·newstackcall or from runtime·morestack when a new
// stack segment is needed. Allocate a new stack big enough for // stack segment is needed. Allocate a new stack big enough for
// m->moreframesize bytes, copy m->moreargsize bytes to the new frame, // m->moreframesize bytes, copy m->moreargsize bytes to the new frame,
...@@ -195,9 +544,9 @@ uintptr runtime·maxstacksize = 1<<20; // enough until runtime.main sets it for ...@@ -195,9 +544,9 @@ uintptr runtime·maxstacksize = 1<<20; // enough until runtime.main sets it for
void void
runtime·newstack(void) runtime·newstack(void)
{ {
int32 framesize, argsize, oldstatus; int32 framesize, argsize, oldstatus, oldsize, newsize, nframes;
Stktop *top, *oldtop; Stktop *top, *oldtop;
byte *stk; byte *stk, *oldstk, *oldbase;
uintptr sp; uintptr sp;
uintptr *src, *dst, *dstend; uintptr *src, *dst, *dstend;
G *gp; G *gp;
...@@ -234,7 +583,7 @@ runtime·newstack(void) ...@@ -234,7 +583,7 @@ runtime·newstack(void)
// The call to morestack cost a word. // The call to morestack cost a word.
sp -= sizeof(uintptr); sp -= sizeof(uintptr);
} }
if(StackDebug || sp < gp->stackguard - StackGuard) { if(StackDebug >= 1 || sp < gp->stackguard - StackGuard) {
runtime·printf("runtime: newstack framesize=%p argsize=%p sp=%p stack=[%p, %p]\n" runtime·printf("runtime: newstack framesize=%p argsize=%p sp=%p stack=[%p, %p]\n"
"\tmorebuf={pc:%p sp:%p lr:%p}\n" "\tmorebuf={pc:%p sp:%p lr:%p}\n"
"\tsched={pc:%p sp:%p lr:%p ctxt:%p}\n", "\tsched={pc:%p sp:%p lr:%p ctxt:%p}\n",
...@@ -273,33 +622,47 @@ runtime·newstack(void) ...@@ -273,33 +622,47 @@ runtime·newstack(void)
runtime·gosched0(gp); // never return runtime·gosched0(gp); // never return
} }
if(newstackcall && m->morebuf.sp - sizeof(Stktop) - argsize - 32 > gp->stackguard) { // If every frame on the top segment is copyable, allocate a bigger segment
// special case: called from runtime.newstackcall (framesize==1) // and move the segment instead of allocating a new segment.
// to call code with an arbitrary argument size, if(runtime·copystack) {
// and we have enough space on the current stack. if(!runtime·precisestack)
// the new Stktop* is necessary to unwind, but runtime·throw("can't copy stacks without precise stacks");
// we don't need to create a new segment. nframes = copyabletopsegment(gp);
top = (Stktop*)(m->morebuf.sp - sizeof(*top)); if(nframes != -1) {
stk = (byte*)gp->stackguard - StackGuard; oldstk = (byte*)gp->stackguard - StackGuard;
free = 0; oldbase = (byte*)gp->stackbase + sizeof(Stktop);
} else { oldsize = oldbase - oldstk;
// allocate new segment. newsize = oldsize * 2;
framesize += argsize; if(newsize > runtime·maxstacksize) {
framesize += StackExtra; // room for more functions, Stktop. runtime·printf("runtime: goroutine stack exceeds %D-byte limit\n", (uint64)runtime·maxstacksize);
if(framesize < StackMin) runtime·throw("stack overflow");
framesize = StackMin; }
framesize += StackSystem; copystack(gp, nframes, newsize);
gp->stacksize += framesize; if(StackDebug >= 1)
if(gp->stacksize > runtime·maxstacksize) { runtime·printf("stack grow done\n");
runtime·printf("runtime: goroutine stack exceeds %D-byte limit\n", (uint64)runtime·maxstacksize); runtime·gogo(&gp->sched);
runtime·throw("stack overflow");
} }
stk = runtime·stackalloc(framesize); // TODO: if stack is uncopyable because we're in C code, patch return value at
top = (Stktop*)(stk+framesize-sizeof(*top)); // end of C code to trigger a copy as soon as C code exits. That way, we'll
free = framesize; // have stack available if we get this deep again.
} }
if(StackDebug) { // allocate new segment.
framesize += argsize;
framesize += StackExtra; // room for more functions, Stktop.
if(framesize < StackMin)
framesize = StackMin;
framesize += StackSystem;
gp->stacksize += framesize;
if(gp->stacksize > runtime·maxstacksize) {
runtime·printf("runtime: goroutine stack exceeds %D-byte limit\n", (uint64)runtime·maxstacksize);
runtime·throw("stack overflow");
}
stk = runtime·stackalloc(framesize);
top = (Stktop*)(stk+framesize-sizeof(*top));
free = framesize;
if(StackDebug >= 1) {
runtime·printf("\t-> new stack [%p, %p]\n", stk, top); runtime·printf("\t-> new stack [%p, %p]\n", stk, top);
} }
...@@ -372,3 +735,34 @@ runtime·gostartcallfn(Gobuf *gobuf, FuncVal *fv) ...@@ -372,3 +735,34 @@ runtime·gostartcallfn(Gobuf *gobuf, FuncVal *fv)
{ {
runtime·gostartcall(gobuf, fv->fn, fv); runtime·gostartcall(gobuf, fv->fn, fv);
} }
// Maybe shrink the stack being used by gp.
// Called at garbage collection time.
void
runtime·shrinkstack(G *gp)
{
int32 nframes;
byte *oldstk, *oldbase;
uintptr used, oldsize;
if(gp->syscallstack != (uintptr)nil) // TODO: handle this case?
return;
oldstk = (byte*)gp->stackguard - StackGuard;
oldbase = (byte*)gp->stackbase + sizeof(Stktop);
oldsize = oldbase - oldstk;
if(oldsize / 2 < FixedStack)
return; // don't shrink below the minimum-sized stack
used = oldbase - (byte*)gp->sched.sp;
if(used >= oldsize / 4)
return; // still using at least 1/4 of the segment.
nframes = copyabletopsegment(gp);
if(nframes == -1)
return; // TODO: handle this case. Shrink in place?
copystack(gp, nframes, oldsize / 2);
if(StackDebug >= 1)
runtime·printf("stack shrink done\n");
}
...@@ -76,7 +76,7 @@ enum { ...@@ -76,7 +76,7 @@ enum {
// The minimum stack segment size to allocate. // The minimum stack segment size to allocate.
// If the amount needed for the splitting frame + StackExtra // If the amount needed for the splitting frame + StackExtra
// is less than this number, the stack will have this size instead. // is less than this number, the stack will have this size instead.
StackMin = 8192, StackMin = 4096,
FixedStack = StackMin + StackSystem, FixedStack = StackMin + StackSystem,
// Functions that need frames bigger than this use an extra // Functions that need frames bigger than this use an extra
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
void runtime·sigpanic(void); void runtime·sigpanic(void);
int32 int32
runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, uintptr *pcbuf, int32 max, void (*callback)(Stkframe*, void*), void *v, bool printall) runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, uintptr *pcbuf, int32 max, bool (*callback)(Stkframe*, void*), void *v, bool printall)
{ {
int32 i, n, nprint, line; int32 i, n, nprint, line;
uintptr x, tracepc; uintptr x, tracepc;
...@@ -140,8 +140,10 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, ...@@ -140,8 +140,10 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
if(pcbuf != nil) if(pcbuf != nil)
pcbuf[n] = frame.pc; pcbuf[n] = frame.pc;
if(callback != nil) if(callback != nil) {
callback(&frame, v); if(!callback(&frame, v))
return n;
}
if(printing) { if(printing) {
if(printall || runtime·showframe(f, gp)) { if(printall || runtime·showframe(f, gp)) {
// Print during crash. // Print during crash.
......
...@@ -19,7 +19,7 @@ void runtime·sigpanic(void); ...@@ -19,7 +19,7 @@ void runtime·sigpanic(void);
// collector (callback != nil). A little clunky to merge these, but avoids // collector (callback != nil). A little clunky to merge these, but avoids
// duplicating the code and all its subtlety. // duplicating the code and all its subtlety.
int32 int32
runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, uintptr *pcbuf, int32 max, void (*callback)(Stkframe*, void*), void *v, bool printall) runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, uintptr *pcbuf, int32 max, bool (*callback)(Stkframe*, void*), void *v, bool printall)
{ {
int32 i, n, nprint, line; int32 i, n, nprint, line;
uintptr tracepc; uintptr tracepc;
...@@ -151,8 +151,10 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, ...@@ -151,8 +151,10 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
if(pcbuf != nil) if(pcbuf != nil)
pcbuf[n] = frame.pc; pcbuf[n] = frame.pc;
if(callback != nil) if(callback != nil) {
callback(&frame, v); if(!callback(&frame, v))
return n;
}
if(printing) { if(printing) {
if(printall || runtime·showframe(f, gp)) { if(printall || runtime·showframe(f, gp)) {
// Print during crash. // Print during crash.
......
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