; swap64(old,new): switch coroutines global swap64 swap64: ; Save preserved registers to old stack push rdi push rbp push rbx push r12 push r13 push r14 push r15 ; Save old stack pointer mov [rdi],rsp ; Load new stack pointer mov rsp,[rsi] ; Restore preserved regs from new stack pop r15 pop r14 pop r13 pop r12 pop rbx pop rbp pop rdi ret
typedef void (*coroutine_run_t)(void *arg); void coroutine_exit(void) { std::cout<<"Coroutine has exited. Goodbye.\n"; exit(0); } class coroutine { public: long *stack; // top of coroutine's stack long *stack_alloc; // allocated memory for stack // Used to make main into a coroutine coroutine() { stack=0; stack_alloc=0; } // Used to create a new coroutine coroutine(coroutine_run_t run,void *arg,int stacksize=1024*1024) { stack_alloc=new long[stacksize/sizeof(long)]; stack=&stack_alloc[stacksize/sizeof(long)-1]; // top of stack *(--stack)=(long)coroutine_exit; // coroutine cleanup *(--stack)=(long)run; // user's function to run (rop style!) *(--stack)=(long)arg; // user's function argument (rdi) for (int saved=0;saved<6;saved++) *(--stack)=0xdeadbeef; // initial values for saved registers } // Cleanup ~coroutine() { delete[] stack_alloc; } }; extern "C" void swap64(coroutine *old_co,coroutine *new_co); coroutine *main_co; coroutine *sub_co; void sub(void *arg) { int i; // random local, to see stack pointer std::cout<<" Inside sub-coroutine. Stack="<<&i<<endl; swap64(sub_co,main_co); // back to main std::cout<<" Back in sub. Stack="<<&i<<endl; swap64(sub_co,main_co); // back to main } long foo(void) { int i; std::cout<<"Making coroutines. Stack="<<&i<<std::endl; main_co=new coroutine(); sub_co=new coroutine(sub,0); std::cout<<"Switching to coroutine"<<std::endl; swap64(main_co,sub_co); std::cout<<"Back in main from coroutine. Stack="<<&i<<std::endl; swap64(main_co,sub_co); std::cout<<"Any questions?"<<std::endl; return 0; }On my machine, this prints:
Making coroutines. Stack=0x7fffffffe61c Switching to coroutine Inside sub-coroutine. Stack=0x2aaaab8f2fe4 Back in main from coroutine. Stack=0x7fffffffe61c Back in sub. Stack=0x2aaaab8f2fe4 Any questions? Program complete. Return 0 (0x0)The only bummer here is the nonportable assembly language.
/** User-level threads example. Threads created using UNIX "ucontext" functions. */ #include <stdio.h> #include <stdlib.h> #include <ucontext.h> /* for makecontext/swapcontext routines */ /* Tiny little user-level thread library */ typedef void (*threadFn)(void); /* Create a new user-level thread to run this function */ void makeThread(ucontext_t *T,threadFn fn) { getcontext(T); /* Allocate a stack for the new thread */ int stackLen=32*1024; char *stack=new char[stackLen]; printf("New thread stack is at %p\n",stack); T->uc_stack.ss_sp=stack; T->uc_stack.ss_size=stackLen; T->uc_stack.ss_flags=0; makecontext(T,fn,0); } /* Context structures for two user-level threads. */ ucontext_t A,B; enum {runTimes=20, delayLen=1*1000*1000}; int total=0; void runA(void) { int somelocal; printf("Thread A: stack at %p\n",&somelocal); for (int n=0;n<runTimes;n++) { for (int i=0;i<delayLen;i++) {} printf("A (it %d) (total %d)\n",n,total); total++; swapcontext(&A,&B); /* switch from thread A to thread B */ } printf("Thread A finished. Exiting program.\n"); exit(1); } void runB(void) { int somelocal; printf("Thread B: stack at %p\n",&somelocal); for (int n=0;n<runTimes;n++) { for (int i=0;i<delayLen;i++) {} printf(" B (it %d) (total %d)\n",n,total); total++; swapcontext(&B,&A); /* switch from thread B to thread A */ } } long foo() { makeThread(&A,runA); makeThread(&B,runB); setcontext(&A); /* Start running thread A first */ return 0; }
If you use
this in a real project, you should use a real library like boost::coroutines
or libconcurrency.