[comp.lang.c] From Modula to Oberon

peter@sugar.UUCP (Peter da Silva) (03/25/88)

In article <1139@PT.CS.CMU.EDU>, edw@IUS1.CS.CMU.EDU (Eddie Wyatt) writes:
>>
>>  The debate about CLU iterators misses an important point: CLU iterators are
>>coroutines, which C does not have.

Coroutines in 'C' are easy to implement, though. Why, this whole O/S we're
reading news on (UNIX) is written as a set of coroutines in 'C'. (yes, it's
a simplification... but not much of one).

I'd like to see the following functions become standard in 'C':

COROUTINE -- Build a jmp_buf for a coroutine.

int coroutine(jump, entry, stack, stacksize);
struct jmp_buf *jump;
void (*entry)();
char *stack;
int stacksize;

This sets up the stack and jmp_buf so that a call to "longjmp(jmp_buf)"
will appear to be a call to entry(). It will return an error only if the
stack isn't large enough for a small routine that does nothing but call
the following function:

int switch(from, to, status)
struct jmp_buf *from, *to;
int status;
{
	int code;

	if(!(code=setjmp(from)))
		longjmp(to, status);

	return code;
}

Voila! Co-routines! Lightweight processes (best if you have the Berkeley
signal handler, I guess, so you could run it off alarms...):

struct proc {
	struct proc *next;
	struct proc *prev;
	char *stack;
	struct jmp_buf context;
};

struct proc *runq;	/* doubly linked circular queue */

sleep()
{
	struct proc *self;
	/* do nothing if no procs or I'm alone */
	if(!runq)
		return;
	if(runq->next == runq)
		return;
	self = runq;
	runq = runq->next;
	switch(&self->context, &runq->context, 1);
}

int spawn(entry, stacksize)
void (*entry)();
int stacksize;
{
	struct proc *p;

	if(!(p = malloc(sizeof *p)))
		return 0;
	if(!(p->stack = malloc(stacksize))) {
		free(p);
		return 0;
	}
	if(!coroutine(p, entry, p->stack, stacksize)) {
		free(p->stack);
		free(p);
		return 0;
	}
	p->stacksize = p;
	p->next = runq->next;
	p->prev = runq;
	runq->next->prev = p;
	runq->next = p;
	return p;
}

int delete(p) /* note, this version doesn't allow a process to delete itself! */
struct process *p;
{
	if(p==runq)
		return 0;

	p->next->prev = p->prev;
	p->prev->next = p->next;
	free(p->stack);
	free(p);
}
-- 
-- Peter da Silva  `-_-'  ...!hoptoad!academ!uhnix1!sugar!peter
-- Disclaimer: These U aren't mere opinions... these are *values*.

james@cantuar.UUCP (J. Collier) (03/29/88)

Expires:

Sender:

Followup-To:

Distribution:

Keywords:


Peter da Silva (peter@sugar.UUCP) writes:
>....
>I'd like to see the following functions become standard in 'C':
>....
>COROUTINE -- Build a jmp_buf for a coroutine.
>....
>This sets up the stack and jmp_buf so that a call to "longjmp(jmp_buf)"
>will appear to be a call to entry().
> [implementation outlines deleted]

   I seem to remember a minor war in the letters to 'Software Practice
and Experience' [I think] on this subject a couple of years back.
(Wasn't it peaceful in the days when it took a month or two to get the
next installment..)

   Correct me if I'm wrong, but I find that on some machines (well, on
BSD Vaxen anyway) setjmp()/longjmp() can't be used to implement
coroutines because longjmp() unwinds the stack destructively while
checking for botches. A small amount of in-line assembly language is
therefore necessary for transferring control.

   I agree with Peter's view that coroutines should be supported in the
C library. As he says, coroutine packages are quite easy to write and they
are indispensible for certain classes of application (I wanted one originally
for a window server/multiplexor).

   The current situation where everybody brews their own isn't really
acceptable. The programs aren't portable, and the semantics differ
sufficiently to make things confusing for the reader. The details will
have to be thrashed out before a standard is defined.

   Pre-empting threads which share a common data space are probably not
a good idea for most purposes - the synchronisation problems usually
outweigh any advantages. Leave true multitasking to the operating system
and keep to protected data spaces if possible, unless you enjoy wrapping
semaphore primitives around every second line of code. (OK, I've just spent
2 years doing this on the Mac, but that's different - no OS processes).

   Threads with explicit sleep() calls and round-robin transfer - as
suggested by Peter - are one way of organising things. I suppose it's
a matter of personal taste, but I prefer a system where the 'new_coroutine()'
call returns a pointer to a structure which contains at least the context
information and the stack base pointer, a reduced form of Peter's 'proc'
structure. This makes it easier to tailor your use of coroutines; sometimes
you want to transfer control explicitly rather than set up a threads system,
and you therefore need some way to identify coroutines throughout their
lifespan. The switch()/transfer()/resume() call keeps track of the
current coroutine through a static pointer, and hence needs no 'from'
parameter.

   There are other issues, such as how best to set up the routines so that
they exit cleanly, whether the original context should be set up as
a coroutine, and whether to support special transfers such as resume_caller()
or resume_<some default>(). Comments?

-------------------------
James Collier              Internet(ish):  james@cantuar.uucp
Computer Science Dept.,    UUCP:           {watmath,munnari,mcvax}!cantuar!james
University of Canterbury,  Spearnet/Janet: j.collier@nz.ac.canty
Christchurch, New Zealand. Office: +64 3 482 009 x8356  Home: +64 3 554 025