peter@ficc.uu.net (Peter da Silva) (08/12/89)
This is a followup to a discussion on threads on comp.unix.wizards... Isn't it about time that there was some effort made to provide a standard coroutine library in C. I realise that some systems (ones with strict hardware stacks) wouldn't be able to implement this, but it'd be workable on a wide variety of systems... First, let's define a structure containing a co-routine's current context. This structure is machine dependent, and contains a co-routines stack and the register set when it's not running. context = cocreate(func, stacksize, ...); This creates a context for a coroutine. The actual arguments need to be hashed out, but obviously need an initial PC and entry point. The rest of the arguments could be passed directly to the coroutine when it's started up. cocall(context); This transfers control to the coroutine until it calls cocall(). It's roughly equivalent to doing a setjmp() in the current context and a longjmp() into the context of the new routine. In fact on a lot of systems it could even be implemented that way. I believe that's how the V7 UNIX kernel did it. Libraries like the BSD one where longjmp unwinds the stack would of course have to have an explicit implementation. codelete(context); This deletes the context and cleans up any resources it was using. These routines together would allow the portable implementation of threads on a wide variety of systems, if they became widespread. These are strictly non-preemptive threads, but that's an advantage if you want to call the C library from more than the mainline... routines in libc are not even vaguely reentrant, but they are almost all (with the exception of strtok() and a few others) serially reusable. -- Peter da Silva, Xenix Support, Ferranti International Controls Corporation. Business: peter@ficc.uu.net, +1 713 274 5180. | "The sentence I am now Personal: peter@sugar.hackercorp.com. `-_-' | writing is the sentence Quote: Have you hugged your wolf today? 'U` | you are now reading"
karl@haddock.ima.isc.com (Karl Heuer) (08/14/89)
In article <5663@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: >Isn't it about time that there was some effort made to provide a standard >coroutine library in C. Wasn't there such a library (cofork(), cowait(), etc.) for the pdp11 back in V6 or early V7? I never used it, but I recall having seen it described. Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint
peter@ficc.uu.net (Peter da Silva) (08/14/89)
In article <14281@haddock.ima.isc.com>, karl@haddock.ima.isc.com (Karl Heuer) writes: > Wasn't there such a library (cofork(), cowait(), etc.) for the pdp11 back in > V6 or early V7? I never used it, but I recall having seen it described. There's no reference to it in my 2.9BSD manual, but that's hardly "early V7". I'm thinking of writing code for this, but I'd like to know what's already out there. -- Peter da Silva, Xenix Support, Ferranti International Controls Corporation. Business: peter@ficc.uu.net, +1 713 274 5180. | "The sentence I am now Personal: peter@sugar.hackercorp.com. `-_-' | writing is the sentence Quote: Have you hugged your wolf today? 'U` | you are now reading"
henry@utzoo.uucp (Henry Spencer) (08/14/89)
In article <5667@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: >> Wasn't there such a library (cofork(), cowait(), etc.) for the pdp11 back in >> V6 or early V7? I never used it, but I recall having seen it described. > >There's no reference to it in my 2.9BSD manual, but that's hardly "early V7". I remember it -- dimly -- from V6. It definitely wasn't in any V7 that I saw. -- V7 /bin/mail source: 554 lines.| Henry Spencer at U of Toronto Zoology 1989 X.400 specs: 2200+ pages. | uunet!attcan!utzoo!henry henry@zoo.toronto.edu
bvs@light.uucp (Bakul Shah) (08/15/89)
In article <5667@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: > >I'm thinking of writing code for this, but I'd like to know what's already >out there. Rob Warnock and I built a simple simulation toolkit a few years ago. The toolkit routines could be grouped in three conceptual layers, with layer N depending on layer N-1. 3: simulation kernel 2: condition variables and priority-queues 1: coroutines To build a complete simulation a user would provide layer 4 consisting of output display and monitoring routines and routines that model the simulated objects themselves. Layers 2 & 3 interfaces were stolen from Concurrent Euclid, layer 1 interface from BLISS. In this note I will focus on the lowest layer. For more info on the simulation kit please see our paper "A simple simulation toolkit in C" in Proceedings of USENIX conference, Summer 1984, Salt Lake City, Utah. The coroutine interface consisted of three routines: struct process * co_create(stack_address, stack_size, exit_handler, function, arg1, arg2, .. argN); Initialize a stack in the given space and return a new process (that is what we called the co-routine handler). The stack is setup so that the first time someone does a co_resume on this process, `function' is entered as if it had been called as a normal routine. `exit_handler', which could be NULL, gets called when this process exits (or returns from function). It can be used for any cleanups on exit. co_resume(other_process) Resume `other process'. If other_process was never called, this call will start it up. Else, this co_resume continues where other_process had done a co_resume. The caller of co_resume is suspended at this point. co_exit(return_value) Cleanup any magic co_create had to do and call any user specified handler in co_create. Co_exit gets called automatically if the top level function returns. Struct process starts out with machine specific area to store its state but an application could extend this struct to stash info specific to it (our simulation kit made use of this feature). We had to implement co_resume and co_exit in (68000) assembly language and co_create in highly machine depenedent C code. The upper two layers were completely machine independent. If I were to do this again today, I would change a couple of things to remove some machine dependencies from the interface: struct co_routine * co_create(stack_descr, exit_handler, function, argc, argv); `stack_descr' hides machine specific info to build new stacks. There are some processors (such as the amd29000) which require multiple stacks. Different environments may have different constraints on such stacks, and it is best to provide a separate, machine specific call to create a co- routine stack. Our co-routine interface is strictly uniprocessing: co_resume suspends its caller so at any given time only one process is ``running''. I would add a routine to resume another process without suspending the caller to allow for a multi-processor implementation. Resuming a running process would return an error. Note that we can't have co_suspend at this level because there is *no implicit* scheduling. A process must explicitly hand over the processor it is running on to someone else. In our scheme the second layer implemented condition queues and provided wait() and signal(). Wait() put a process on a condition queue. Signal() suspended the signalling process and ran the signalled process. This in turn was sufficient to implement mutual exclusion. I would probably extend wait to allow a process to wait on multiple conditions. When any one of the wait conditions becomes true, the process is taken off from all queues and run. Different schemes appropriate in different situations may also be built on top of the co-routine layer, which is why I would be hesitant to extend it any further. -- Bakul Shah <..!{ames,sun,ucbvax,uunet}!amdcad!light!bvs>
idall@augean.OZ (Ian Dall) (08/15/89)
In article <14281@haddock.ima.isc.com> karl@haddock.ima.isc.com (Karl Heuer) writes: >In article <5663@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: >>Isn't it about time that there was some effort made to provide a standard >>coroutine library in C. > >Wasn't there such a library (cofork(), cowait(), etc.) for the pdp11 back in >V6 or early V7? I never used it, but I recall having seen it described. My V6 manual (chapter 7) documents crfork, crread, crwrite, crexch and cprior. It says "The activation record for each function execution is dynamically allocated rather than stacked." It goes on to say that there is a big overhead in doing this! It is however possible to specify an alternate stack as an optional argument to crfork and avoid this overhead. The scheme seems workable but portable implimentation is no doubt impossible and it may not be possible to impliment at all on some machines. Would making the coroutine functions compiler "builtin" inline functions allow implimentation on a larger variety of machines? What sort of problems do you want coroutines for? I seem to have survived about 14 years of programming without ever using them! -- Ian Dall life (n). A sexually transmitted disease which afflicts some people more severely than others. idall@augean.oz
richard@aiai.ed.ac.uk (Richard Tobin) (08/15/89)
In article <14281@haddock.ima.isc.com> karl@haddock.ima.isc.com (Karl Heuer) writes: >Wasn't there such a library (cofork(), cowait(), etc.) for the pdp11 back in >V6 or early V7? I never used it, but I recall having seen it described. Yes, it's in section VII (user-maintained subroutines) of the Sixth Edition (May 75) manual. The routines were actually called crfork(), crexit(), crread(), crwrite() crexch() and crprior(). -- Richard -- Richard Tobin, JANET: R.Tobin@uk.ac.ed AI Applications Institute, ARPA: R.Tobin%uk.ac.ed@nsfnet-relay.ac.uk Edinburgh University. UUCP: ...!ukc!ed.ac.uk!R.Tobin
peter@ficc.uu.net (Peter da Silva) (08/16/89)
In article <563@augean.OZ>, idall@augean.OZ (Ian Dall) writes: > The scheme seems workable > but portable implimentation is no doubt impossible No, which is why the basic routines need to be standardised, at least to the point where people implementing them on different compilers can agree to the names and calling conventions. > and it may not be possible > to impliment at all on some machines. True, but machines like that are likely to cause problems with varargs, setjmp/longjmp, and so on as well. > Would making the coroutine functions > compiler "builtin" inline functions allow implimentation on a larger variety > of machines? Probably not. The difficulty is dealing with exotic stacks, not where the code is done. As a thought experiment consider the case of making the cocall from a subroutine of the coroutine... if inline code was needed that would not be possible. > What sort of problems do you want coroutines for? I seem to have survived about > 14 years of programming without ever using them! Mainly for the sort of event-loop programming endemic to windowing systems. It would be easier to code this sort of application if minithreads were available. -- Peter da Silva, Xenix Support, Ferranti International Controls Corporation. Business: peter@ficc.uu.net, +1 713 274 5180. | "The sentence I am now Personal: peter@sugar.hackercorp.com. `-_-' | writing is the sentence Quote: Have you hugged your wolf today? 'U` | you are now reading"
joe@modcomp.UUCP (08/16/89)
> Isn't it about time that there was some effort made to provide a standard > coroutine library in C. At the last meeting, POSIX 1003.4 voted in a thread proposal which was worded in such a way that a coroutine implementation, done entirely with library changes, would be able to meet the standard. Thus, while this standard is technically an operating system rather than a C standard, it potentially could become the de-facto way to do coroutines/threads in C under any environment. joe korty uunet!modcomp!joe
poser@csli.Stanford.EDU (Bill Poser) (08/16/89)
Another application of the same sorts of facilities used to implement coroutines is for generator functions, i.e. functions that generate a new member of some set each time they are called. Such functions are useful for pattern matching. I have also found them useful when I needed to obtain strings contained in heterogeneous data structures (e.g. arrays and hash tables containing different kinds of objects) and sort and format information coming from all of them. To get each string you call the generator function for one of the types of data. The generator functions know the details of how the data is stored and how to traverse the data structure. But they all return objects of the same type (strings) and you keep control over storage allocation, sorting, formatting etc. in one place. Generator functions can be implemented by hand by using static variables to save the necessary state information, but this gets hairy very quickly. If one can suspend the function rather than return from it (which means, essentially, saving the stack frame) generators easy to write. This is a nice facility of ICON.
gwyn@smoke.BRL.MIL (Doug Gwyn) (08/16/89)
In article <5695@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: >> What sort of problems do you want coroutines for? I seem to have survived about >> 14 years of programming without ever using them! >Mainly for the sort of event-loop programming endemic to windowing systems. >It would be easier to code this sort of application if minithreads were >available. Ah, you raise an interesting point. EVENT LOOPS SUCK. Rob Pike has some interesting comments on this and suggestions for a better approach in his article "A Concurrent Window System" in the Spring 1989 issue of Computing Systems (The Journal of the USENIX Association) (Vol. 2 No. 2).
peter@ficc.uu.net (Peter da Silva) (08/16/89)
Generators in 'C'... I guess you could use coroutines to implement
something like Icon generators, but Smalltalk generators might be
easier. There's more to Icon generators than the function itself...
how do you do !every write(factor(192))!?
Generators in Smalltalk are implemented differently than in Icon.
You set up methods for getting the first and next element of the
sequence, and use instance variables (if necessary) to maintain
state. This is more conventional, and wouldn't require coroutines.
gen = NewGenerator(Factor);
x = gen->First(192);
while(x) {
Write(x);
x = gen->Next();
}
This is a job (removes glasses, or is that classes?) for C++?
--
Peter da Silva, Xenix Support, Ferranti International Controls Corporation.
Business: peter@ficc.uu.net, +1 713 274 5180. | "The sentence I am now
Personal: peter@sugar.hackercorp.com. `-_-' | writing is the sentence
Quote: Have you hugged your wolf today? 'U` | you are now reading"
peter@ficc.uu.net (Peter da Silva) (08/16/89)
In article <10747@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes: > In article <5695@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: > >> What sort of problems do you want coroutines for? [actually someone else wrote that] > >Mainly for the sort of event-loop programming endemic to windowing systems. > Ah, you raise an interesting point. EVENT LOOPS SUCK. #pragma begin arthur-dent-voice Yes, well that's the point isn't it? #pragma end arthur-dent-voice Going to threads is a moderately nice way to work around the problem. -- Peter da Silva, Xenix Support, Ferranti International Controls Corporation. Business: peter@ficc.uu.net, +1 713 274 5180. | "The sentence I am now Personal: peter@sugar.hackercorp.com. `-_-' | writing is the sentence Quote: Have you hugged your wolf today? 'U` | you are now reading"
poser@csli.Stanford.EDU (Bill Poser) (08/17/89)
In article <5710@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: >Generators in 'C'... I guess you could use coroutines to implement >something like Icon generators, but Smalltalk generators might be >easier. There's more to Icon generators than the function itself... >how do you do !every write(factor(192))!? It seems to me that this question confuses implementation of generators, that is, having multiple calls to factor() return the several factors, and goal-oriented evaluation, where the "every" construct (as well as others, some implicit) causes write() to be called until it fails which in turn causes factor() to be called until it fails, which happens when it runs out of results to return. Goal-oriented evaluation a la Icon does indeed require more than saving stack frames, but implementing the generator itself doesn't. >Generators in Smalltalk are implemented differently than in Icon. >You set up methods for getting the first and next element of the >sequence, and use instance variables (if necessary) to maintain >state. This is more conventional, and wouldn't require coroutines. I'm not sure I understand how this solves the problem. Using instance variables is pretty much like using statics in C to store the state information, but the real problem is in implementing the method for getting the next element in the sequence. It is that method that is going to need the state information, and when the control structure gets even moderately complicated, saving the state information without saving the whole stack frame gets to be a pain. For example, here is a generator that returns pointers to strings matching a given regular expression. The array commands contains structures whose name member is a character pointer. comcnt is the number of array elements. char * AproposBuiltins(re) regexp *re; { static int i = 0; char *retval; while(i < comcnt){ if(regexec(re,retval = commands[i].name)){ ++i; return(retval); } else ++i; } /* If we get here there are no more matches */ i = 0; /* Reset for next search */ return((char *) 0); } We make the loop index static and we also have to complicate the control structure a bit over a simple loop traversal, but it isn't too bad. How about traversing a two dimensional array? We would not only have to make both array indices static, but we would have to keep track of whether it was time to increment the outer loop index or whether we should reenter the inner loop right away. But if we save the stack frame, including not only the auto variables but the program counter, we have all the information we need no matter how complex the control structure. What is similar to coroutine support is language support for the equivalent of the Icon "suspend" primitive, which returns a value but saves the stack frame. To my (limited) knowledge, Smalltalk does not provide support for this. Isn't the Smalltalk method of implementing generators just doing it by hand as in the above C example, with instance variables playing the role of statics?
peter@ficc.uu.net (Peter da Silva) (08/17/89)
In article <10109@csli.Stanford.EDU>, poser@csli.Stanford.EDU (Bill Poser) writes: > I'm not sure I understand how this solves the problem. Using instance > variables is pretty much like using statics in C to store the state > information, Not really, because it can be concurrently reused. It's more like using a handle to hold the state information. But as you say it can get to be a pain if the state information is complex... > For example, here is a generator that returns pointers to strings > matching a given regular expression. Looks good, and coroutines could be used to implement this... you'd want to pass an extra argument, the context of the calling routine. Let's say the coroutine functions are: context = co_create(entry, stack, ...); Creates context, passes arguments to it, transfers control to it. stuff = co_call(context, stuff); /* void *stuff */ stuff will be return value from co_call in other context. status = co_delete(context); Deletes context. It's an error to delete yourself. context = co_self(); Returns own context. Then we'd call AproposBuiltins like... AproposContext = co_create(AproposBuiltins, DEFAULT_STACK, re, co_self()); while(co_call(AproposContext, NULL)) { ... } co_delete(AproposContext); (changes marked with !, additions with +) > char * ! AproposBuiltins(re, context) > regexp *re; + context *context; > { ! int i = 0; > char *retval; + + co_call(context, NULL); /* return from co_create */ > > while(i < comcnt){ ! if(regexec(re,retval = commands[i].name)) ! co_call(context, retval); ! ++i; > } > > /* If we get here there are no more matches */ > > i = 0; /* Reset for next search */ ! co_call(context, NULL); + blow_up(); /* Should never get here!!!!! */ > } -- Peter da Silva, *NIX support guy @ Ferranti International Controls Corporation. Biz: peter@ficc.uu.net, +1 713 274 5180. Fun: peter@sugar.hackercorp.com. `-_-' "Optimization is not some mystical state of grace, it is an intricate act U of human labor which carries real costs and real risks." -- Tom Neff
hinton@netcom.UUCP (Greg Hinton) (08/18/89)
In article <5663@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: >Isn't it about time that there was some effort made to provide a standard >coroutine library in C. >These routines together would allow the portable implementation of threads >on a wide variety of systems, if they became widespread. Isn't this precisely what the language Concurrent C attempts? "Concurrent C is the result of an effort to enhance C so that it can be used to write concurrent programs that can run efficiently on single computers, on loosely-coupled distributed computer networks, or on tightly-coupled multiprocessors. Concurrent C is an upward-compatible extension of C. These extensions include mechanisms for the declaration and creation of processes, for process synchronization and interaction, and for process termination and abortion." _The_Concurrent_C_Programming_Language_, Gehani & Roome, p. xii. Anyone know any more about this language? -- Greg Hinton INET: hinton@netcom.uucp UUCP: ...!uunet!apple!netcom!hinton
peter@ficc.uu.net (Peter da Silva) (08/18/89)
In article <2151@netcom.UUCP>, hinton@netcom.UUCP (Greg Hinton) writes: > In article <5663@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: > >Isn't it about time that there was some effort made to provide a standard > >coroutine library in C. > >These routines together would allow the portable implementation of threads > >on a wide variety of systems, if they became widespread. > Isn't this precisely what the language Concurrent C attempts? Concurrent C is a different language. I can't take a couple of pages of code and use them to implement threads for a C compiler and library I don't have the source to. In many C compilers I wouldn't even need to write any assembly code, since setjmp/longjmp can be used to implement the context switch. This is not nearly as ambitious a project as Concurrent C. -- Peter da Silva, *NIX support guy @ Ferranti International Controls Corporation. Biz: peter@ficc.uu.net, +1 713 274 5180. Fun: peter@sugar.hackercorp.com. `-_-' "Optimization is not some mystical state of grace, it is an intricate act U of human labor which carries real costs and real risks." -- Tom Neff
schwartz@shire.cs.psu.edu (Scott Schwartz) (08/19/89)
In article <2151@netcom.UUCP> hinton@netcom.UUCP (Greg Hinton) writes: [ concurrent c ... ] | Anyone know any more about this language? Yes... anything in particular you want to know? If you are looking for an endorsement, psu cs uses it in some operating systems classes. (Having used things like concurrent euclid in the past.) -- Scott Schwartz <schwartz@shire.cs.psu.edu>
bvs@light.uucp (Bakul Shah) (08/20/89)
In article <5773@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: > > In many C compilers I wouldn't even need >to write any assembly code, since setjmp/longjmp can be used to implement >the context switch. You are better off not depending on setjmp/longjmp for this because 1. In addition to jumping where a setjmp() was done, longjmp() pops zero or more frames (activation records) off the top of frame stack. 2. A longjmp() is guaranteed to work only if it is called on the same stack as its corresponding setjmp(). 3. On some machines, one must rely on the stack being the same to properly implement setjmp/longjmp. 4. Some implementations use longjmp()'s property of *shortening* the stack to catch an invalid use of longjmp (e.g. `longjmp botch' message on 4.3 bsd). 5. In a context switch you don't want to pop frames, you want to switch stacks. Even in cases where you can get away with using them for coroutine `context switch' now, the compiler vendor may rightfully decide to check for 4. above in a later version. It would be nice to have a standardized (but optional) coroutine facility (if an implementation provides it, one can depend on a standard inteface and behavior) but there is not reason to mix them up with setjmp/longjmp. By keeping the two concepts separate you would not add any additional constraints on or be constrained by setjmp/longjmp. -- Bakul Shah <..!{ames,sun,ucbvax,uunet}!amdcad!light!bvs>
peter@ficc.uu.net (Peter da Silva) (08/21/89)
In article <1989Aug19.230124.860@light.uucp>, bvs@light.uucp (Bakul Shah) writes: > In article <5773@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: > > In many C compilers I wouldn't even need > >to write any assembly code, since setjmp/longjmp can be used to implement > >the context switch. > You are better off not depending on setjmp/longjmp for this because I'm not depending on setjmp/longjmp. I'm just pointing out that, *in many C compilers* it is possible to use setjmp/longjmp to implement the context switch. In my original posting on this subject I went into more detail on this subject. I am well aware of the Berkeley VAX compiler's stack unwinding on a longjmp. > Even in cases where you can get away with using them for coroutine > `context switch' now, the compiler vendor may rightfully decide to > check for 4. above in a later version. True. The compiler vendor can also change their calling sequence and invalidate your low level context-switch routines. A new version of the compiler is effectively a new compiler and a new porting effort for routines like this. Hopefully if they become standardised the compiler vendor will include them in the new release. > It would be nice to have a standardized (but optional) coroutine > facility (if an implementation provides it, one can depend on a standard > inteface and behavior) but there is not reason to mix them up with > setjmp/longjmp. You should pop back a few stack levels yourself :->, and have a look at my original proposal. I do keep the two concepts seperate, but designing things so a naive implementation of setjmp/longjmp (store all regs in jmp_buf in setjmp, restore them and do a return in longjmp) can be used to bootstrap the system is IMO a good idea. -- Peter da Silva, *NIX support guy @ Ferranti International Controls Corporation. Biz: peter@ficc.uu.net, +1 713 274 5180. Fun: peter@sugar.hackercorp.com. `-_-' "Optimization is not some mystical state of grace, it is an intricate act U of human labor which carries real costs and real risks." -- Tom Neff
sam@lfcs.ed.ac.uk (S. Manoharan) (08/23/89)
In article <5773@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: > > In many C compilers I wouldn't even need >to write any assembly code, since setjmp/longjmp can be used to implement >the context switch. I have a question. Consider these functions: event1() event2() { { . . . . . . get_resourceX(); . . . /*resume:*/ . . . . . . } } event1() requsts for resourceX. If resourceX is not available then I want event1() to suspend, and some other event, say event2() to start. Perhaps, event2() could make the resourceX available. Once the resourceX is available, I want the function event1() to start from the /*resume:*/ point. (The code upto the /*resume:*/ point in event1() should not be re-executed) Intutively, it appears the co-routines could be applied in this context. Am I correct? And then, is there an easy way to tackle this problem? Since there could be a number of event?()s, I think setjmp() and longjmp() would be of no help. Janet: sam@uk.ac.ed.lfcs S. Manoharan Uucp : ..!mcvax!ukc!lfcs!sam Dept of Computer Science Arpa : sam%lfcs.ed.ac.uk@nsfnet-relay.ac.uk University of Edinburgh Voice: 031-667 5076 (home) Edinburgh EH9 3JZ UK.
mouse@mcgill-vision.UUCP (der Mouse) (08/28/89)
In article <10109@csli.Stanford.EDU>, poser@csli.Stanford.EDU (Bill Poser) writes: > In article <5710@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: >> Generators in Smalltalk are implemented differently than in Icon. >> You set up methods for getting the first and next element of the >> sequence, and use instance variables (if necessary) to maintain >> state. This is more conventional, and wouldn't require coroutines. > I'm not sure I understand how this solves the problem. Using > instance variables is pretty much like using statics in C to store > the state information, but the real problem is in implementing the > method for getting the next element in the sequence. [...What's > needed is] the equivalent of the Icon "suspend" primitive, which > returns a value but saves the stack frame. Right. > Isn't the Smalltalk method of implementing generators just doing it > by hand as in the above C example, with instance variables playing > the role of statics? Doing a generator in C this way works fine - provided only one copy of each generator is active at once. Suppose we write a generator that returns numbers in sequence, from 0 up: generate_Z() { static int i = 0; return(i++); } This works fine if we use it in simple applications. But try to use two copies of it to drive two nested loops, for example, and you'll get a little surprise.... The `instance variable' approach, while not as nice as the Icon way, is nicer than using statics in C. (C can do this, but as far as I can see you need to fake instances: typedef struct generator_Z_state { int i; } ZZZ; /* typedef for brevity */ char *new_generator_Z() { struct ZZZ z; /* char * for data hiding */ z = (ZZZ *)malloc(sizeof(ZZZ)); z->i = 0; return((char *)z); } int generator_Z(state) char *state; { return(((ZZZ *)state)->i++); } destroy_generator_Z(state) char *state; { free(state); } which is a bit ugly, but sometimes necessary. This would of course be nicer in some other language, C++ for example, which is better suited to object-oriented coding.) der Mouse old: mcgill-vision!mouse new: mouse@larry.mcrcim.mcgill.edu
peter@ficc.uu.net (Peter da Silva) (08/28/89)
In article <214@castle.ed.ac.uk>, sam@lfcs.ed.ac.uk (S. Manoharan) writes: > In article <5773@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: > >In many C compilers I wouldn't even need to write any assembly code, since > >setjmp/longjmp can be used to implement the context switch. [ problem deleted to save space ] > Intutively, it appears the co-routines could be applied in this > context. Am I correct? I believe so. > And then, is there an easy way to tackle this problem? Have a look at my article a couple of weeks back where I introduced this subject, then implement coroutines :->. In existing, portable, C... no. > Since there could be a number of event?()s, I think setjmp() > and longjmp() would be of no help. The naive implementation of setjmp() is to save all the registers in jmp_buf. The corresponding naive implementation of longjmp is to restore those registers and return. If this implementation is used, it should be possible to create a fake stack frame and register set in a jmp_buf and tranfer control to it using longjmp. This is, of course, hideously non-portable. But if the calling sequence for coroutines can be standardized then this non-portable code could be hidden inside co_call() and friends. -- Peter da Silva, *NIX support guy @ Ferranti International Controls Corporation. Biz: peter@ficc.uu.net, +1 713 274 5180. Fun: peter@sugar.hackercorp.com. `-_-' "Just once I'd like to meet an alien menace that isn't immune to bullets" 'U` -- The Brigadier, Dr Who.
gwyn@smoke.BRL.MIL (Doug Gwyn) (08/29/89)
In article <5899@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: >If this implementation is used, it should be possible to create >a fake stack frame and register set in a jmp_buf and tranfer control to it >using longjmp. You need separate (non-overlapping) stacks, not just a stack frame. Otherwise the coroutines will step on each other's <DELETED>.
peter@ficc.uu.net (Peter da Silva) (08/29/89)
In article <10861@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes: > In article <5899@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes: > >If [simple setjmp] is used, it should be possible to create > >a fake stack frame and register set in a jmp_buf and tranfer control to it > >using longjmp. > You need separate (non-overlapping) stacks, not just a stack frame. > Otherwise the coroutines will step on each other's <DELETED>. You're right, I should have spelled that out. I sort of assumed that anyone who had been following this discussion would figure that out... but of course someone who was following the discussion wouldn't have asked the question in the first place. I'll try to be clearer in the future and not assume that people will glark stuff like that from context. -- Peter da Silva, *NIX support guy @ Ferranti International Controls Corporation. Biz: peter@ficc.uu.net, +1 713 274 5180. Fun: peter@sugar.hackercorp.com. `-_-' "Just once I'd like to meet an alien menace that isn't immune to bullets" 'U` -- The Brigadier, Dr Who.
buck@siswat.UUCP (A. Lester Buck) (11/20/89)
A while back, I posted to comp.sources.wanted for coroutine packages for C. I got a few leads, found a few good sources on my own, and ended up implementing my own small package from a year old posting by Peter da Silva (thanks Peter!) that I had saved but forgotten in my comp.lang.c archives (in a file called "coroutines" yet). ================== From jls@attctc I have found two sets of code, one very rough, but perhaps a good starting point. First, the PC Technical Journal did a series on Multi Tasking a couple of years ago -- a lot of BBS systems have the TJOS c source files. They just cover starting and stopping multiple threads scheduled by the timer tick. No critical sections, no blocking of one thread for I/O, no protection from multiple calls to DOS, etc. (I also have heard rumors that the Doctor Dobbs Journal ran a similar series, comlete with code.) I found another package (on a big xenix BBS system) called CTASK. This was a rather complete multitasking package for C under MS DOS, and seems to have most of the stuff missing from TJOS. Here's an excerpt from the docs: CTask A Multitasking Kernel for C Version 1.1 Released 88-07-01 Public Domain Software written by Thomas Wagner Patschkauer Weg 31 D-1000 Berlin 33 West Germany BIXmail: twagner Introduction CTask is a set of routines that allow your C program to execute functions in parallel, without you having to build in sophisti- cated polling and switching schemes. CTask handles the switching of processor time with a priority based, preemptive scheduler, and provides a fairly complete set of routines for inter-task communi- cation, event signalling, and task interlocking. CTask also in- cludes a number of drivers for MS-DOS that build on the basic functions to allow you to include serial I/O, printer buffering, and concurrent access to DOS functions into your programs with little programming effort. An Example To illustrate one possible use of CTask, let me elaborate on the following example. Say you just finished your nifty telecommuni- cations program, complete with download protocols, scripts, and everything. But wouldn't it be nice to be able to print the file you just downloaded while receiving the next, and edit a comment to some previous message without interrupting the transmission? So you take those editor routines from a previous project, plug them in, and - oops, how do you switch back and forth between editing, communication and printing? The answer to this is CTask. CTask allows your C program to do many different things at the same time by switching the processor between the tasks you define. And since most of the time your program is waiting for some slow device (like the human hand) to provide feedback, this switching is com- pletely transparent, and will not noticeably slow your program Thomas Wagner, Berlin,Germany This package is in an arc file about 200K bytes called CTASK11A.ARC. I found it on the XBBS author's BBS, but have seen it on other systems that run XBBS. This package sounds like what you are looking for. ================== From ...!uw-beaver!sumax!steven!cac I use the coroutine package from the Icon folks at U. Ariz. ================== I also got a note from someone at the Austin Codeworks that one of their packages also includes coroutine support, but I didn't save that message. The November 1989 issue of Computer Language magazine has a multitasking theme, including an article on "Pre-emptive Tasking in C++". This article refers to a previous article from October 1988, "A Multitasking Kernel for C Programmers", which is a nice non-premptive set of code, is quite portable and supports threads, pipes, messages, semaphores, and more. The only suggestion I would make for this system is to use the Microsoft C5.1 /Gs switch to disable stack probes, instead of the kludge of setting STKHQQ=0 in the assembly piece. I didn't get the CL issue until after I had another working implementation, which is given here. I probably would have used the Oct 88 CL code if I was to do it again. Here is an old posting on coroutines by Peter da Silva. The part I don't understand here is that this appears to be a complete C implementation of a coroutine package, but the context switch function is called switch()! Peter, can you design this stuff on the fly as you type it in (I am impressed!!!), or was this translated from another language? I can't see how it ever compiled in C. ====================== From nuchat!sugar!peter Fri Mar 25 07:06:36 1988 Path: siswat!nuchat!sugar!peter From: peter@sugar.UUCP (Peter da Silva) Newsgroups: comp.lang.misc,comp.unix.wizards,comp.lang.c Subject: Re: From Modula to Oberon Summary: C almost supports coroutines. Her's what it needs. Message-ID: <1758@sugar.UUCP> Date: 25 Mar 88 13:06:36 GMT References: <2827@enea.se><1557@pasteur.Berkeley.Edu><3764@bloom-beacon.MIT.EDU><1139@PT.CS.CMU.EDU> Organization: Sugar Land UNIX - Houston, TX Lines: 100 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*. ======================= Here is my implementation of the above design, with assembly language support for Microsoft C5.1. This code extends the setjmp/longjmp routines in Microsoft C to include the stack segment in the jmp_buf. Do not #include <setjmp.h> in any code using this package. Compile all your code with "/AL /Au /Gs". (Large model, stack segment reg != data segment reg, and disable stack probes.) Since this code mallocs the stack to be used for each process, /Au prevents fun stuff like "i=1; array[1] != array[i]". I used this for a terminal emulator product that supports N independent serial input streams. A typical test program that exercises the coroutines is included at the end, though you will need to change the test functions called for serial input. This code went into a quick demo that works fine, but may still have some subtle bugs, and some of the fancy junk I put in the setjmp/longjmp assembly on stack overflow has not been tested. If anyone does find bugs, I would appreciate hearing about them. (I did this for a client under contract, but since I got the design from Usenet, it only seems right to give something useful back.) A. Lester Buck ...!texbell!moray!siswat!buck -------------------cut here--------------------cut here--------------------- #To unpack, delete all lines before this and feed to /bin/sh echo setjmp.asm 1>&2 sed -e 's/^X//' >setjmp.asm <<'END' X; @(#)setjmp.asm 1.3 89/11/19 15:27:46 X X; setjmp.asm - setjmp, longjmp, coroutine support X X page 55,132 X Xreturn_offset EQU word ptr [bp+0] Xreturn_segment EQU word ptr [bp+2] XARG_jmpbuf EQU dword ptr [bp+4] XARG_status EQU word ptr [bp+8] X Xjmpbuf struc Xbp_save dw ? ; bp Xdi_save dw ? ; di Xsi_save dw ? ; si Xstack_offset dw ? ; stack Xstack_segment dw ? ; address Xentry_offset dw ? ; entry Xentry_segment dw ? ; address Xds_save dw ? ; ds Xjmpbuf ends X X.model large X.code X X X PUBLIC _setjmp X_setjmp PROC X; destroys ax, bx, cx, dx X mov ax, bp ; save registers, but not on stack X mov bp, sp ; set frame pointer X mov dx, ds X lds bx, ARG_jmpbuf ; DS:BX -> jmpbuf X mov [bx].bp_save, ax X mov [bx].di_save, di X mov [bx].si_save, si X mov [bx].stack_offset, sp X mov cx, ss ; save stack segment X mov [bx].stack_segment, cx X mov cx, return_offset X mov [bx].entry_offset, cx X mov cx, return_segment X mov [bx].entry_segment, cx X mov [bx].ds_save, dx X mov ds, dx ; restore registers X mov bp, ax X xor ax, ax X retf X_setjmp ENDP X X X PUBLIC _longjmp X_longjmp PROC X; destroys ax, bx, cx, dx X mov bp, sp ; set frame pointer X mov ax, ARG_status ; set return value X or ax, ax ; insure non-zero X jnz skip X inc ax Xskip: X lds bx, ARG_jmpbuf ; DS:BX -> jmpbuf X mov di, [bx].di_save X mov si, [bx].si_save X mov dx, [bx].stack_segment X mov ss, dx ; do stack switch X mov sp, [bx].stack_offset ; new sp must be next instruction X mov bp, sp ; new frame pointer X mov cx, [bx].entry_offset ; set saved return address X mov return_offset, cx X mov cx, [bx].entry_segment X mov return_segment, cx X mov bp, [bx].bp_save X mov ds, [bx].ds_save X retf X_longjmp ENDP X X X X; X; int coroutine(context, entry, stack, stacksize) X; X; struct jmp_buf *context; X; void (*entry)(); X; char *stack; X; int stacksize; X; X; Setup stack and entry in jmp_buf so that a call to X; "longjump(jmp_buf)" will appear to be a call to entry(). X; It will return an error only if the stack isn't large X; enough for a small routine that does nothing but call X; swap(). X; X; returns true(1) on success, false(0) on failure X; X X PUBLIC _coroutine XARG_context EQU dword ptr [bp+4] XARG_entry_offset EQU word ptr [bp+8] XARG_entry_segment EQU word ptr [bp+10] XARG_stack_offset EQU word ptr [bp+12] XARG_stack_segment EQU word ptr [bp+14] XARG_stacksize EQU word ptr [bp+16] X X X_coroutine PROC X mov ax, bp X mov bp, sp X cmp ARG_stacksize, 050h ; minimum stacksize X jnb stack_ok X mov bp, ax X xor ax, ax ; error return X retf Xstack_ok: X mov dx, ds X lds bx, ARG_context X mov [bx].bp_save, ax ; save BP and DS, restore later X mov [bx].ds_save, dx X mov cx, ARG_entry_offset X mov [bx].entry_offset,cx X mov cx, ARG_entry_segment X mov [bx].entry_segment,cx X mov ax, ARG_stack_offset X add ax, ARG_stacksize X jnc nocarry Xfixss: ; correct for SP overflow X ; make largest possible SP X mov cx, 4 X shr ax, cl X inc ax X add ARG_stack_segment, ax ; fixup SS X mov ax, ARG_stack_offset ; subtract one paragraph from SP X and ax, 0Fh X sub ax, 10h Xnocarry: X sub ax, 4 ; make room for return address X ; in longjmp X ; (ignore possible carry) X mov [bx].stack_offset, ax X mov cx, ARG_stack_segment X mov [bx].stack_segment, cx X mov ax, return_segment ; save return address in registers X mov cx, return_offset X; mov ss, [bx].stack_segment ; switch to new stack X; mov sp, [bx].stack_offset X; push ax ; form return address on new stack X; push cx X X X mov dx, [bx].ds_save ; restore registers X mov ax, [bx].bp_save X mov [bx].bp_save, 0 ; set indeterminate registers to zero X mov [bx].si_save, 0 X mov [bx].di_save, 0 X mov ds, dx X mov bp, ax X mov ax, 1 ; successful return X retf X_coroutine ENDP X X END END echo corout.h 1>&2 sed -e 's/^X//' >corout.h <<'END' X/* @(#)corout.h 1.2 89/11/19 15:28:07 */ X X#define STACKSIZE (0x2000) /* I use 8K stack/process */ X Xstruct jmp_buf { X int bp; X int di; X int si; X void (*stack)(); X void (*entry)(); X int ds; X}; X Xstruct proc { X struct proc *next; X struct proc *prev; X struct jmp_buf context; X char *stack; X int stacksize; X}; X Xextern struct proc *runq; /* doubly linked circular queue */ X Xextern struct proc *spawn(); Xextern void sleep(); END echo corout.c 1>&2 sed -e 's/^X//' >corout.c <<'END' X/* @(#)corout.c 1.4 89/11/19 15:27:56 */ X X#include <stdio.h> X#include <malloc.h> X#include "corout.h" X#include "screen.h" X Xstatic Xint Xswap(from, to, status) Xstruct jmp_buf *from, *to; Xint status; X{ X int code; X X if(!(code=setjmp(from))) { X longjmp(to, status); X } X X return code; X} X Xvoid Xsleep() X{ X struct proc *self; X /* do nothing if no procs or I'm alone */ X if (!runq) { X return; X } X if (runq->next == runq) { X return; X } X X self = runq; X runq = runq->next; X swap(&self->context, &runq->context, 1); X} X Xstruct proc * Xspawn(entry, stacksize) Xvoid (*entry)(); Xint stacksize; X{ X struct proc *p; X X if(!(p = (struct proc *)malloc(sizeof *p))) { X printf("spawn: malloc struct proc failed\n"); X return 0; X } X if(!(p->stack = malloc(stacksize))) { X printf("spawn: malloc stack area failed\n"); X free(p); X return 0; X } X if(!coroutine(&p->context, entry, p->stack, stacksize)) { X printf("spawn: coroutine returned failure\n"); X free(p->stack); X free(p); X return 0; X } X p->stacksize = stacksize; X if (!runq) { /* if this is first process */ X runq = p->next = p->prev = p; X return p; X } X p->next = runq->next; X p->prev = runq; X runq->next->prev = p; X runq->next = p; X return p; X} X X/* this version does allow a process to delete itself, */ X/* but not necessarily successfully... */ Xint Xdelete(p) Xstruct proc *p; X{ X p->next->prev = p->prev; X p->prev->next = p->next; X free(p->stack); X free(p); X} END echo tstco.c 1>&2 sed -e 's/^X//' >tstco.c <<'END' X/* @(#)tstco.c 1.2 89/11/19 15:02:11 */ X X/* testco.c - test coroutine package */ X X#include "corout.h" X Xstruct proc *runq; /* doubly linked circular queue */ X Xvoid Xkilltime() X{ X long i; X for (i=0; i<100000; i++) { X } X} X Xvoid Xscreen0() X{ X int c; X while(1) { X if ((c=serial_in(0)) != -1) { X printf("screen0: got %x \'%c\'\n", c); X } else { X sleep(); X } X } X} X Xvoid Xscreen1() X{ X int c; X while(1) { X if ((c=serial_in(1)) != -1) { X printf("screen1: got %x \'%c\'\n", c); X } else { X sleep(); X } X } X} X Xvoid Xscreen2() X{ X int c; X while(1) { X if ((c=serial_in(2)) != -1) { X printf("screen2: got %x \'%c\'\n", c); X } else { X sleep(); X } X } X} X Xvoid Xscreen3() X{ X int c; X while(1) { X if ((c=serial_in(3)) != -1) { X printf("screen3: got %x \'%c\'\n", c); X } else { X sleep(); X } X } X} X Xstruct jmp_buf jump; Xint ret; X Xmain() X{ X if ((ret=setjmp(&jump)) != 0) { X exit(ret); X } X X if (!spawn(screen0, 0x1000)) { X printf("spawn screen1 failed\n"); X longjmp(&jump, 1); X } X if (!spawn(screen1, 0x1000)) { X printf("spawn screen1 failed\n"); X longjmp(&jump, 1); X } X if (!spawn(screen2, 0x1000)) { X printf("spawn screen1 failed\n"); X longjmp(&jump, 1); X } X if (!spawn(screen3, 0x1000)) { X printf("spawn screen2 failed\n"); X longjmp(&jump, 1); X } X longjmp(&runq->context); X printf("screen0 returned, exiting\n"); X longjmp(&jump, 0); X} X END -- A. Lester Buck ...!texbell!moray!siswat!buck
peter@ficc.uu.net (Peter da Silva) (11/21/89)
Yeh, that was pretty much designed on the fly. It never occurred to me that it might not be a smart idea to call the switch routine switch(). I have done a similar implementation on Forth, about 8 years ago now, so it wasn't done completely cold. It *was* a redesign, though, and without access to the original code. Thanks for the ego boost. You could have kept quiet about the "switch()" business, though... :-> -- `-_-' Peter da Silva <peter@ficc.uu.net> <peter@sugar.lonestar.org>. 'U` -------------- +1 713 274 5180. "ERROR: trust not in UUCP routing tables" -- MAILER-DAEMON@mcsun.EU.net