5

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?

MathuSum Mut
  • 2,765
  • 3
  • 27
  • 59
  • 1
    No, it's not possible. Rewrite without recursion. – molbdnilo Mar 15 '16 at 10:23
  • 3
    Turn recursion into iteration, problem solved. – Kerrek SB Mar 15 '16 at 10:24
  • 2
    And no, you can't extend the call stack into "the cloud" either. – molbdnilo Mar 15 '16 at 10:25
  • 2
    You don't have to put up with a fixed-size call stack. See http://stackoverflow.com/a/1053159/120163 You sure don't want to push to a disk, where access times go from nS to milliseconds, which would buy you a 1000x slow down. – Ira Baxter Mar 15 '16 at 10:26
  • doesn't gcc already support fragmented stacks on linux? In which case, the solution is simply to use a modern version of gcc. – Richard Hodges Mar 15 '16 at 10:32
  • Not a real question. In this century, with 64 bits CPU's, there's essentially no justification for exceeding the call stack. But even if you did, the virtual memory manager of the OS would _automatically_ swap the untouched parts of the stack out to disk. Heck, even Linux, OSX and Windows didn't have a real problem with this when they were 32 bits only. The "manual management" stuff harks back to Mac OS 9, which is a late 20th century OS. – MSalters Mar 15 '16 at 14:53
  • If any operating systems out there are letting stacks grow bounded only by available virtual memory, it's news to me. Stacks are usually tiny and arbitrarily bounded because there's no justification for exceeding the call stack, period. – zeromus Mar 15 '16 at 21:27

2 Answers2

6

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.

zeromus
  • 1,648
  • 13
  • 14
1

It's feasible. You need a bit of assembly to manipulate the stack pointer as there's no standardized way of accessing it from C++ directly (as far as I know). Once you are there you can point to your memory page and take care of swapping memory in and out. There are already libraries out there doing it for you.

On the other hand if the system provider considered that paging memory or the other virtual memory techniques would not work/be worth on the platform they probably had a very good reason (most likely it would be incredibly slow). Try to get your solution to work without the recursion or change it to make the recursion fit into what's available. Even a less efficient implementation would end up faster than your disk paged recursion.

Sorin
  • 11,863
  • 22
  • 26