Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extend call stack to disk in C++?

Tags:

c++

callstack

When it comes to massively-recursive method calls, the call-stack size has to be extended by modifying the appropriate compiler parameters in order to avoid stack-overflow.

Let's consider writing a portable application whose layout is simple enough so that its users need only possess minimal technical knowledge, so manual virtual memory configuration is out of question.

Running massively-recursive methods (behind the scenes obviously) may result in the call-stack limit being surpassed, especially if the machine the application is running on is limited memory-wise.

Enough chit-chat: In C++ is it possible to manually extend the call-stack to disk in case memory is (almost) full?

like image 643
MathuSum Mut Avatar asked Mar 15 '16 10:03

MathuSum Mut


1 Answers

It may just barely be possible.

Use a coroutine library. With that, you allocate your own stack out of the heap. Restructure your code to track how deep it is in its callstack, and when it gets dangerously deep, create a new cothread and switch into that instead. When you run out of heap memory, freeze old cothreads and free their memory. Of course, you better be sure to unfreeze them to the same address--so I suggest you allocate their stacks yourselves out of your own arena that you can control. In fact it may be easier to just reuse the same piece of memory for the cothread stack and swap them in and out one at a time.

It's certainly easier to rewrite your algorithm to be non-recursive.

This may be an example of it working, or it may just print the right answer on accident:

#include <stdio.h>
#include "libco.h"

//byuu's libco has been modified to use a provided stack; it's a simple mod, but needs to be done per platform
//x86.c:
////if(handle = (cothread_t)malloc(size)) {
//handle = (cothread_t)stack;

//here we're going to have a stack on disk and have one recursion's stack in RAM at a time
//I think it may be impossible to do this without a main thread controlling the coroutines, but I'm not sure.

#define STACKSIZE (32*1024)
char stack[STACKSIZE];

FILE* fpInfiniteStack;
cothread_t co_mothership;

#define RECURSING 0
#define EXITING 1
int disposition;

volatile int recurse_level;

int call_in_cothread( int (*entrypoint)(int), int arg);

int fibo_b(int n);
int fibo(int n)
{
    if(n==0)
        return 0;
    else if(n==1) 
        return 1;
    else {
        int a = call_in_cothread(fibo,n-1);
        int b = call_in_cothread(fibo_b,n-2);
        return a+b;
    }
}
int fibo_b(int n) { printf("fibo_b\n"); return fibo(n); } //just to make sure we can call more than one function

long filesize;
void freeze()
{
    fwrite(stack,1,STACKSIZE,fpInfiniteStack);
    fflush(fpInfiniteStack);
    filesize += STACKSIZE;
}
void unfreeze()
{
    fseek(fpInfiniteStack,filesize-STACKSIZE,SEEK_SET);
    int read = fread(stack,1,STACKSIZE,fpInfiniteStack);
    filesize -= STACKSIZE;
    fseek(fpInfiniteStack,filesize,SEEK_SET);
}

struct 
{
    int (*proc)(int);
    int arg;
} thunk, todo;

void cothunk()
{
    thunk.arg = thunk.proc(thunk.arg);
    disposition = EXITING;
    co_switch(co_mothership);
}

int call_in_cothread(int (*proc)(int), int arg)
{
    if(co_active() != co_mothership)
    {
        todo.proc = proc;
        todo.arg = arg;

        disposition = RECURSING;
        co_switch(co_mothership);
        //we land here after unfreezing. the work we wanted to do has already been done.
        return thunk.arg;
    }

NEXT_RECURSE:
    thunk.proc = proc;
    thunk.arg = arg;
    cothread_t co = co_create(stack,STACKSIZE,cothunk);
    recurse_level++;
NEXT_EXIT:
    co_switch(co);

    if(disposition == RECURSING)
    {
        freeze();
        proc = todo.proc;
        arg = todo.arg;
        goto NEXT_RECURSE;
    }
    else
    {
        recurse_level--;
        unfreeze();
        if(recurse_level==0)
            return thunk.arg; //return from initial level of recurstion
        goto NEXT_EXIT;
    }

    return -666; //this should not be possible
}

int main(int argc, char**argv)
{
    fpInfiniteStack = fopen("infinite.stack","w+b");
    co_mothership = co_active();
    printf("result: %d\n",call_in_cothread(fibo,10));
}

Now you just need to detect how much memory's in the system, how much of it is available, how big the callstack is, and when the callstack's exhausted, so you know when to deploy the infinite stack. That's not easy stuff for one system, let alone doing it portably. It might be better to learn how the stack is actually meant to be used instead of fighting it.

like image 90
zeromus Avatar answered Oct 12 '22 02:10

zeromus