replace coroutine to setjmp

This commit is contained in:
Hojun-Cho 2024-07-22 21:36:18 +09:00
parent aea52f9e63
commit 3181653d92
13 changed files with 67 additions and 943 deletions

View File

@ -1,18 +1,11 @@
CC=tcc
CFLAGS= -c -Wall -g -Wextra
SRC:=$(wildcard *.c)
OBJ:=$(SRC:.c=.o)
all: gb
clean:
make clean -C co/
rm -f $(OBJ)
rm -f *.o
rm -f gb
%.o: %.c gb.h
$(CC) $(CFLAGS) -o $@ $<
gb: $(OBJ)
make all -C co/
$(CC) co/*.o *.o -lucontext -lSDL2 -g -o gb
$(CC) jmp.S *.c -lSDL2 -o gb

View File

@ -1,15 +0,0 @@
CC=tcc
CFLAGS= -c -Wall -g
SRC:=$(wildcard *.c)
OBJ:=$(SRC:.c=.o)
all: co
clean:
rm -f $(OBJ)
%.o: %.c
$(CC) $(CFLAGS) -o $@ $<
co: $(OBJ)

View File

@ -1,2 +0,0 @@
# coroutine library
https://github.com/Hojun-Cho/coroutine

View File

@ -1,356 +0,0 @@
#include "taskimpl.h"
Channel*
newchan(int elemsize, int bufsize)
{
Channel* c;
c = malloc(sizeof *c + bufsize * elemsize);
if (c == 0) {
exit(1);
}
*c = (Channel){
.elemsize = elemsize,
.bufsize = bufsize,
.nbuf = 0,
.buf = (uchar*)&c[1],
};
return c;
}
void
deletechan(Channel* c)
{
if (c == 0)
return;
free(c->name);
free(c->asend.a);
free(c->arecv.a);
free(c);
}
static void
addarray(Altarray* a, Alt* alt)
{
if (a->n == a->m) {
a->m += 16;
a->a = realloc(a->a, a->m * sizeof a->a[0]);
if (a->a == nil) {
exit(1);
}
}
a->a[a->n++] = alt;
}
static void
amove(void* dst, void* src, uint n)
{
if (dst) {
if (src == nil)
memset(dst, 0, n);
else
memmove(dst, src, n);
}
}
static void
delarray(Altarray* a, int i)
{
--a->n;
a->a[i] = a->a[a->n];
}
static enum chan_op
otherop(enum chan_op op)
{
return (CHANSND + CHANRCV) - op;
}
static Altarray*
chanarray(Channel* c, enum chan_op op)
{
switch (op) {
default:
return nil;
case CHANSND:
return &c->asend;
case CHANRCV:
return &c->arecv;
}
}
/*
* 3 players: sender, reciver, 'channel itself'
* if chann.empty
* send(send to reciver);
*/
static void
altcopy(Alt* s, Alt* r)
{
Channel* c;
uchar* cp;
if (s == nil && r == nil)
return;
assert(s != nil); /* sender must not nil */
c = s->c;
if (s->op == CHANRCV) {
Alt* t = s;
s = r;
r = t;
}
assert(s == nil || s->op == CHANSND); /* sender */
assert(r == nil || r->op == CHANRCV); /* reciver */
/* Channel is unbufferd */
if (s && r && c->nbuf == 0) {
amove(r->v, s->v, c->elemsize);
return;
}
if (r) {
/* recive buffred data first */
cp = c->buf + c->off * c->elemsize;
amove(r->v, cp, c->elemsize);
--c->nbuf;
if (++c->off == c->bufsize)
c->off = 0;
}
if (s) {
cp = c->buf + (c->off + c->nbuf) % c->bufsize * c->elemsize;
amove(cp, s->v, c->elemsize);
++c->nbuf;
}
}
static void
altdeque(Alt* a)
{
Altarray* ar;
ar = chanarray(a->c, a->op);
if (ar == nil) {
exit(1);
}
for (uint i = 0; i < ar->n; ++i) {
if (ar->a[i] == a) {
delarray(ar, i);
return;
}
}
/* can't find self in altdq */
exit(1);
}
static void
altqueue(Alt* a)
{
Altarray* ar;
ar = chanarray(a->c, a->op);
addarray(ar, a);
}
static void
altalldeque(Alt* a)
{
for (int i = 0; a[i].op != CHANEND && a[i].op != CHANNOBLK; i++) {
if (a[i].op != CHANNOP)
altdeque(&a[i]);
}
}
static void
altexec(Alt* a)
{
Altarray* ar;
Channel* c;
c = a->c;
ar = chanarray(c, otherop(a->op));
if (ar && ar->n) {
int i = rand() % ar->n; /* find any other palyer */
Alt* other = ar->a[i];
altcopy(a, other); /* copy data to other's buffer */
altalldeque(other->xalt); /* delete because now request is completed */
other->xalt[0].xalt = other;
taskready(other->task);
} else
altcopy(a, nil); /* if channel is unbufferd then do nothing */
}
static int
altcanexec(Alt* a)
{
Channel* c;
if (a->op == CHANNOP)
return 0;
c = a->c;
if (c->bufsize == 0) {
Altarray* ar = chanarray(c, otherop(a->op));
return ar && ar->n;
}
switch (a->op) {
default:
return 0;
case CHANSND:
return c->nbuf < c->bufsize;
case CHANRCV:
return c->nbuf > 0;
}
}
int
chanalt(Alt* a)
{
int i, j, ncan, n, canblock;
Task* t;
assertstack(512);
for (i = 0; a[i].op != CHANEND && a[i].op != CHANNOBLK; ++i)
;
n = i;
canblock = (a[i].op == CHANEND);
t = taskrunning;
for (i = 0; i < n; ++i) {
a[i].task = t;
a[i].xalt = a;
}
ncan = 0;
for (i = 0; i < n; i++) {
if (altcanexec(&a[i])) {
ncan++;
}
}
if (ncan) {
j = rand() % ncan;
for (i = 0; i < n; ++i) {
if (altcanexec(&a[i])) {
if (j-- == 0) {
altexec(&a[i]);
return i;
}
}
}
}
if (canblock == 0)
return -1;
for (i = 0; i < n; ++i) {
if (a[i].op != CHANNOP)
altqueue(&a[i]);
}
taskswitch();
return a[0].xalt - a; /* calculate offset */
}
static int
_chanop(Channel* c, int op, void* p, int canblock)
{
Alt ar[2];
ar[0] = (Alt){
.c = c,
.op = op,
.v = p,
};
ar[1] = (Alt){
.op = canblock ? CHANEND : CHANNOBLK,
};
if (chanalt(ar) < 0)
return -1;
return 1;
}
int
chansend(Channel* c, void* v)
{
return _chanop(c, CHANSND, v, 1);
}
int
chanbsend(Channel* c, void* v)
{
return _chanop(c, CHANSND, v, 0);
}
int
chanrecv(Channel* c, void* v)
{
return _chanop(c, CHANRCV, v, 1);
}
int
chanbrecv(Channel* c, void* v)
{
return _chanop(c, CHANSND, v, 0);
}
int
chansendp(Channel* c, void* v)
{
return _chanop(c, CHANSND, &v, 1);
}
void*
chanrecvp(Channel* c)
{
void* v;
_chanop(c, CHANRCV, &v, 1);
return v;
}
int
channbsendp(Channel* c, void* v)
{
return _chanop(c, CHANSND, (void*)&v, 0);
}
void*
channbrecvp(Channel* c)
{
void* v;
_chanop(c, CHANRCV, &v, 0);
return v;
}
int
chansendul(Channel* c, ulong v)
{
return _chanop(c, CHANSND, &v, 1);
}
ulong
chanrecvul(Channel* c)
{
ulong v;
_chanop(c, CHANRCV, &v, 1);
return v;
}
int
channbsendul(Channel* c, ulong v)
{
return _chanop(c, CHANSND, &v, 0);
}
int
channbrecvul(Channel* c, ulong v)
{
return _chanop(c, CHANRCV, &v, 0);
}
int
channbsend(Channel* c, void* v)
{
return _chanop(c, CHANSND, v, 0);
}
int
channbrecv(Channel* c, void* v)
{
return _chanop(c, CHANRCV, v, 0);
}

View File

@ -1,60 +0,0 @@
#include "taskimpl.h"
int
_qlock(QLock* l, int block)
{
if (l->owner == nil) {
l->owner = taskrunning;
return 1;
}
if (!block)
return 0;
addtask(&l->waiting, taskrunning);
taskswitch(); /* wait until own lock */
if (l->owner != taskrunning) {
/* TODO: */
exit(1);
}
return 1;
}
void
qlock(QLock* l)
{
_qlock(l, 1);
}
int
canqlock(QLock* l)
{
return _qlock(l, 0);
}
void
qunlock(QLock* l)
{
Task* ready;
if (l->owner == nil) {
/* TODO: */
exit(1);
}
l->owner = ready = l->waiting.head;
if (l->owner != nil) {
deltask(&l->waiting, ready);
taskready(ready);
}
}
QLock*
newqlock()
{
QLock* l;
l = malloc(sizeof *l);
if (l == nil)
exit(1);
l->owner = 0;
l->waiting = (Tasklist){ 0 };
return l;
}

View File

@ -1,54 +0,0 @@
#include "taskimpl.h"
void
tasksleep(Rendez* r)
{
addtask(&r->waiting, taskrunning);
if (r->l)
qunlock(r->l);
taskswitch();
if (r->l)
qlock(r->l);
}
static int
_taskwakeup(Rendez* r, int all)
{
int i;
Task* t;
for (i = 0;; ++i) {
if (i == 1 && !all)
break;
if ((t = r->waiting.head) == nil)
break;
deltask(&r->waiting, t);
taskready(t);
}
return i;
}
int
taskwakeup(Rendez* r)
{
return _taskwakeup(r, 0);
}
int
taskwakeupall(Rendez* r)
{
return _taskwakeup(r, 1);
}
Rendez*
newrendez(QLock* l)
{
Rendez* r;
r = malloc(sizeof *r);
if (r == nil)
exit(1);
r->l = l;
r->waiting = (Tasklist){ 0 };
return r;
}

253
co/task.c
View File

@ -1,253 +0,0 @@
#include "taskimpl.h"
#include <stdio.h>
int taskcount;
int taskidgen;
int tasknswitch;
int taskexitval;
Task* taskrunning;
ucontext_t taskschedcontext;
Tasklist taskrunqueue;
Task** alltask;
int nalltask;
static void
contextswitch(ucontext_t* from, ucontext_t* to);
void
assertstack(uint n);
static void
taskinfo(int s)
{
int i;
Task* t;
char* extra;
for (i = 0; i < nalltask; ++i) {
t = alltask[i];
if (t == taskrunning)
extra = " (running)";
else if (t->ready)
extra = " (ready)";
else
extra = "";
printf("%s\n", extra);
}
}
static void
taskstart(uint x, uint y)
{
Task* t;
ulong z;
z = x << 16;
z <<= 16;
z |= y;
t = (Task*)z;
t->startfn(t->startarg);
taskexit(0);
}
static Task*
taskalloc(void (*fn)(void*), void* arg, uint stk)
{
Task* t;
sigset_t zero;
uint x, y;
ulong z;
if ((t = malloc(sizeof *t + stk)) == nil) {
exit(1);
}
*t = (Task){
.stk = (uchar*)(&t[1]),
.stksize = stk,
.id = ++taskidgen,
.startfn = fn,
.startarg = arg,
};
sigemptyset(&zero);
sigprocmask(SIG_BLOCK, &zero, &t->uc.uc_sigmask);
if (getcontext(&t->uc)) {
exit(1);
}
t->uc.uc_stack.ss_sp = t->stk + 8;
t->uc.uc_stack.ss_size = t->stksize - 64;
z = (ulong)t;
y = z;
z >>= 16;
x = z >> 16;
makecontext(&t->uc, (void (*)())taskstart, 2, x, y);
return t;
}
int
taskcreate(void (*fn)(void*), void* arg, uint stk)
{
int id;
Task* t;
t = taskalloc(fn, arg, stk);
taskcount++;
id = t->id;
if (nalltask % 64 == 0) {
alltask = realloc(alltask, (nalltask + 64) * sizeof(alltask[0]));
if (alltask == 0) {
exit(1);
}
}
t->alltaskslot = nalltask;
alltask[nalltask++] = t;
taskready(t);
return id;
}
void
taskready(Task* t)
{
t->ready = 1;
addtask(&taskrunqueue, t);
}
void
taskswitch(void)
{
assertstack(0);
contextswitch(&taskrunning->uc, &taskschedcontext);
}
int
taskyield(void)
{
int n;
n = tasknswitch;
taskready(taskrunning);
taskswitch();
return tasknswitch - n - 1;
}
void
taskexit(int val)
{
taskexitval = val;
taskrunning->exiting = 1;
taskswitch();
}
void
taskexitall(int val)
{
exit(val);
}
void
deltask(Tasklist* l, Task* t)
{
if (t->prev)
t->prev->next = t->next;
else
l->head = t->next;
if (t->next)
t->next->prev = t->prev;
else
l->tail = t->prev;
}
void
addtask(Tasklist* l, Task* t)
{
if (l->tail) {
l->tail->next = t;
t->prev = l->tail;
} else {
l->head = t;
t->prev = nil;
}
l->tail = t;
t->next = nil;
}
static void
contextswitch(ucontext_t* from, ucontext_t* to)
{
if (swapcontext(from, to) < 0) {
printf("swapcontext is fail\n");
exit(1);
}
}
void
assertstack(uint n)
{
Task* t;
t = taskrunning;
if ((uchar*)&t <= (uchar*)t->stk || (uchar*)&t - (uchar*)t->stk < 256 + n) {
/* satck over flow */
exit(1);
}
}
static void
taskscheduler(void)
{
int i;
Task* t;
for (;;) {
if (taskcount == 0)
exit(taskexitval);
t = taskrunqueue.head;
if (t == nil) {
/* nothing to do */
exit(1);
}
deltask(&taskrunqueue, t); /* delete from runqueue */
t->ready = 0;
taskrunning = t;
tasknswitch++;
contextswitch(&taskschedcontext, &t->uc);
taskrunning = nil; /* ready for next task */
if (t->exiting) {
taskcount--;
i = t->alltaskslot;
alltask[i] = alltask[--nalltask];
alltask[i]->alltaskslot = i;
free(t);
}
}
}
static char* argv0;
static int taskargc;
static char** taskargv;
int mainstacksize;
static void
taskmainstart(void* v)
{
taskmain(taskargc, taskargv);
}
int
main(int argc, char** argv)
{
struct sigaction sa, osa;
memset(&sa, 0, sizeof(sigaction));
sa.sa_handler = taskinfo;
sa.sa_flags = SA_RESTART;
sigaction(SIGQUIT, &sa, &osa);
/*sigaction(SIGINFO, &sa, &osa);*/
argv0 = argv[0];
taskargc = argc;
taskargv = argv;
mainstacksize = 256 * 1024;
taskcreate(taskmainstart, nil, mainstacksize);
taskscheduler();
exit(0);
}

138
co/task.h
View File

@ -1,138 +0,0 @@
#ifndef _TASK_H_
#define _TASK_H_
typedef struct Task Task;
typedef struct Tasklist Tasklist;
struct Tasklist
{
Task* head;
Task* tail;
};
typedef struct QLock
{
Task* owner;
Tasklist waiting;
} QLock;
void
taskmain(int argc, char** argv);
int
taskyield(void);
void
taskexitall(int val);
int
taskcreate(void (*fn)(void*), void* arg, unsigned int stk);
void
taskready(Task* t);
void
taskexit(int val);
void
taskswitch(void);
void
assertstack(unsigned int n);
void
qlock(QLock*);
int
canqlock(QLock*);
void
qunlock(QLock*);
QLock*
newqlock();
typedef struct Rendez Rendez;
struct Rendez
{
QLock* l;
Tasklist waiting;
};
void
tasksleep(Rendez*);
int
taskwakeup(Rendez*);
int
taskwakeupall(Rendez*);
Rendez*
newrendez(QLock* l);
extern Task* taskrunning;
extern int taskcount;
typedef struct Alt Alt;
typedef struct Altarray Altarray;
typedef struct Channel Channel;
enum chan_op
{
CHANEND,
CHANSND,
CHANRCV,
CHANNOP,
CHANNOBLK,
};
struct Alt
{
Channel* c;
void* v;
enum chan_op op;
Task* task;
Alt* xalt;
};
struct Altarray
{
Alt** a;
unsigned int n;
unsigned int m;
};
struct Channel
{
unsigned int bufsize;
unsigned int elemsize;
unsigned char* buf;
unsigned int nbuf;
unsigned int off;
Altarray asend;
Altarray arecv;
char* name;
};
Channel*
newchan(int elemsize, int bufsize);
void
deletechan(Channel* c);
int
chansend(Channel* c, void* v);
int
chanbsend(Channel* c, void* v);
int
chanrecv(Channel* c, void* v);
int
chanbrecv(Channel* c, void* v);
int
chansendp(Channel* c, void* v);
void*
chanrecvp(Channel* c);
int
channbsend(Channel* c, void* v);
int
channbsendp(Channel* c, void* v);
void*
channbrecvp(Channel* c);
int
channbrecv(Channel* c, void* v);
int
chansendul(Channel* c, unsigned long v);
unsigned long
chanrecvul(Channel* c);
int
channbsendul(Channel* c, unsigned long v);
int
channbrecvul(Channel* c, unsigned long v);
#endif

View File

@ -1,44 +0,0 @@
#include "task.h"
#include <assert.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#define nil ((void*)0)
#define nelem(x) (sizeof(x) / sizeof((x)[0]))
typedef unsigned long ulong;
typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned long long uvlong;
typedef long long vlong;
enum
{
STACK = 8192,
};
struct Task
{
Task* next;
Task* prev;
Task* allnext;
Task* allprev;
ucontext_t uc;
uvlong alarmtime;
uint id;
uchar* stk;
uint stksize;
int exiting;
int alltaskslot;
int ready;
void (*startfn)(void*);
void* startarg;
};
void
deltask(Tasklist* l, Task* t);
void
addtask(Tasklist* l, Task* t);

7
gb.c
View File

@ -1,5 +1,4 @@
#include "gb.h"
#include "co/task.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -146,8 +145,8 @@ colinit(void)
}
}
void
taskmain(int argc, char* argv[])
int
main(int argc, char* argv[])
{
loadrom(romname = argv[1]);
colinit();
@ -155,7 +154,7 @@ taskmain(int argc, char* argv[])
initevent();
meminit();
reset();
taskcreate(pputask, 0, 32768);
ppuinit();
for (;;) {
int t = step();

8
gb.h
View File

@ -134,6 +134,12 @@ enum
enum { NEVENT = 2 };
enum
{
REG_RIP = 7,
REG_RSP = 6,
};
typedef uint8_t u8;
typedef uint16_t u16;
typedef int8_t i8;
@ -210,7 +216,7 @@ hblanktick(void*);
void
ppusync(void);
void
pputask(void*);
ppuinit(void);
/* cpu */
int

30
jmp.S Normal file
View File

@ -0,0 +1,30 @@
.global psetjmp
.type psetjmp,@function
psetjmp:
mov %rbx,(%rdi) /* rdi is jmp_buf, move registers onto it */
mov %rbp,8(%rdi)
mov %r12,16(%rdi)
mov %r13,24(%rdi)
mov %r14,32(%rdi)
mov %r15,40(%rdi)
lea 8(%rsp),%rdx /* this is our rsp WITHOUT current ret addr */
mov %rdx,48(%rdi)
mov (%rsp),%rdx /* save return addr ptr for new rip */
mov %rdx,56(%rdi)
xor %eax,%eax /* always return 0 */
ret
.global plongjmp
.type plongjmp,@function
plongjmp:
xor %eax,%eax
cmp $1,%esi /* CF = val ? 0 : 1 */
adc %esi,%eax /* eax = val + !val */
mov (%rdi),%rbx /* rdi is the jmp_buf, restore regs from it */
mov 8(%rdi),%rbp
mov 16(%rdi),%r12
mov 24(%rdi),%r13
mov 32(%rdi),%r14
mov 40(%rdi),%r15
mov 48(%rdi),%rsp
jmp *56(%rdi)

32
ppu.c
View File

@ -1,15 +1,17 @@
#include "co/task.h"
#include "gb.h"
#include <string.h>
#define ryield() {if(psetjmp(renderjmp) == 0) plongjmp(mainjmp, 1);}
#define myield() {if(psetjmp(mainjmp) == 0) plongjmp(renderjmp, 1);}
#define eat(nc) \
if (cyc <= nc) { \
for (i = 0; i < nc; i++) \
if (--cyc == 0) \
taskyield(); \
ryield(); \
} else \
cyc -= nc;
typedef void *jmp_buf[10];
typedef struct sprite sprite;
struct sprite
{
@ -31,6 +33,7 @@ enum
TILSPR = 0x04,
};
jmp_buf mainjmp,renderjmp;
u8 ppustate, ppuy, ppuw;
u64 hblclock, rendclock;
static int cyc, ppux, ppux0;
@ -39,14 +42,19 @@ sprite spr[10], *sprm;
Var ppuvars[] = {VAR(ppustate), VAR(ppuy), VAR(hblclock), VAR(rendclock),
{nil, 0, 0}};
void
pputask(void* _)
extern int psetjmp(jmp_buf buf);
extern void plongjmp(jmp_buf buf, int v);
static void
ppurender(void)
{
int x, y, i, n, m, win;
u16 ta, ca, chr;
u8 tile;
u32 sr[8], *picp;
ryield();
for (;;) {
eat(6 * 2);
m = 168 + (reg[SCX] & 7);
@ -82,7 +90,7 @@ pputask(void* _)
if (cyc <= 2 * 8) {
for (i = 0; i < 2 * 8; ++i)
if (--cyc == 0)
taskyield();
ryield();
y = ppuy + reg[SCY] << 1 & 14;
ta = 0x1800 | reg[LCDC] << 7 & 0x400 | ppuy + reg[SCY] << 2 & 0x3E0 |
ta & 0x1F;
@ -108,7 +116,7 @@ pputask(void* _)
m = 175 - reg[WX];
goto restart;
}
taskyield();
ryield();
}
}
@ -211,7 +219,7 @@ ppusync(void)
return;
cyc = clock - rendclock;
if (cyc != 0)
taskyield();
myield();
sprites();
rendclock = clock;
}
@ -287,3 +295,13 @@ hblanktick(void* _)
break;
}
}
void
ppuinit(void)
{
static u8 stk[8192];
renderjmp[REG_RSP] = stk + sizeof(stk) - 64;
renderjmp[REG_RIP] = ppurender;
myield();
}