; 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.