.\" --------------------------------------
.\" Oberon System Documentation   AFB 7/90
.\" (c) University of Ulm, SAI, D-7900 Ulm
.\" --------------------------------------
.OH ,,,,
.EH ,,,,
.OF ,Guide for Coroutines,,%,
.EF ,%,,Guide for Coroutines,
.de Pg
.nf
.ie t \{\
.	sp 0.3v
.	ps 9
.	ft CW
.\}
.el .sp 1v
..
.de Pe
.ie t \{\
.	ps
.	ft P
.	sp 0.3v
.\}
.el .sp 1v
.fi
..
.de Tb
.br
.nr Tw \w'\\$1MMM'
.in +\\n(Twu
..
.de Te
.in -\\n(Twu
..
.de Tp
.br
.ne 2v
.in -\\n(Twu
\fI\\$1\fP
.br
.in +\\n(Twu
.sp -1
..
.TL
\s14Guide for Coroutines in Ulm's Oberon System\s0
.AU
Andreas Borchert
.AI
University of Ulm, SAI
Helmholtzstr. 18
D-89069 Ulm
.LP
.DS
.DE
.NH
Introduction
.LP
A simple and powerful coroutine scheme has been offered
in Modula-2 by N. Wirth.
The two basic operations (exported by the \fBSYSTEM\fP module) of Modula-2
are:
.Pg
PROCEDURE NEWPROCESS(proc: PROC;
                     addr: ADDRESS; size: CARDINAL;
                     VAR new: ADDRESS);
PROCEDURE TRANSFER(VAR source, destination: ADDRESS);
.Pe
.B NEWPROCESS
creates a new coroutine with a stack starting at
.I addr
with size
.I size .
The coroutine starts execution by calling the
parameterless global procedure
.I proc .
A handle to the new coroutine is returned in
.I new .
.LP
The first disadvantage of
.B NEWPROCESS
is that coroutines must be parameterized by use of global variables.
This leads to following typical layout:
.Pg
VAR (* global variables *)
   crparams: CoroutineParameters;
   source: ADDRESS; (* current coroutine is called by this one *)
   newcr: ADDRESS; (* coroutine just created by NEWPROCESS *)

PROCEDURE Coroutine;
   VAR
      myparams: CoroutineParameters;
BEGIN
   (* copy parameters and return to calling coroutine *)
   myparams := crparams;
   TRANSFER(newcr, source);
   (* rest of coroutine *)
END Coroutine;

PROCEDURE SetupCoroutine(params: CoroutineParameters; proc: PROC);
   (* create coroutine and pass parameters *)
BEGIN
   NEWPROCESS(proc, addr, size, newcr);
   crparams := params;
   TRANSFER(source, newcr);
END SetupCoroutine;
.Pe
Each new coroutine is started just after creation to allow
parameter copying.
After getting parameters the coroutines transfer immediately back
because the rest of the coroutine typically
requires the existence of the
other coroutines.
.LP
.B NEWPROCESS
and
.B TRANSFER
are found in nearly every Modula-2 implementation and seem to be thus
very portable at first sight.
Nevertheless, the stack size required by the coroutine (parameter
.I size
of
.B NEWPROCESS )
is very implementation dependent.
.LP
.B TRANSFER
has two variable parameters:
.I source
and
.I destination .
This allows two ways of allocating coroutine structures:
.IP \(bu
the address pointing to the coroutine structure remains constant
(e.g. coroutine structure is at beginning of the stack).
.IP \(bu
the coroutine structure is allocated at time of
coroutine suspension (call of
.B TRANSFER );
e.g. at top of stack.
.LP
A special case is introduced by the main coroutine.
The main coroutine is implicitly created during runtime start-off.
The pointer to the coroutine structure of the main coroutine
is set by the first
.B TRANSFER
operation.
Thus, the
.I source
parameter is not an input parameter at least for the first
\fBTRANSFER\fP-operation.
This requires either the second case or the use of 
a prior invisible pointer to the coroutine structure of the main coroutine.
Consequently, the
.I source
parameter can be omitted if at least the pointer to the main coroutine
becomes visible.
.LP
The second case invalidates copies of earlier coroutine pointers
as illustrated by following example:
.Pg
VAR
   main, newcr: ADDRESS;

PROCEDURE Coroutine;
   VAR
      cr: ADDRESS;
BEGIN
   TRANSFER(cr, main);
   (* ... *)
END Coroutine;

PROCEDURE Setup;
BEGIN
   NEWPROCESS(Coroutine, addr, size, newcr);
   TRANSFER(main, newcr);
   (* newcr is no longer valid *)
END Setup;
.Pe
.NH
Ulm's Coroutine Primitives
.LP
.B NEWPROCESS
and
.B TRANSFER
have been replaced by
.Pg
PROCEDURE CRSPAWN(VAR newcr: COROUTINE);

PROCEDURE CRSWITCH(dest: COROUTINE);
.Pe
and an interface module
.I Coroutines
(see below).
.LP
The main change is that coroutines declare themselves to be coroutines
as illustrated by following example:
.Pg
PROCEDURE Coroutine(myparams: CoroutineParameters;
                    VAR newcr: SYSTEM.COROUTINE);
BEGIN
   (* start as normal procedure *)
   SYSTEM.CRSPAWN(newcr); (* return to Setup *)
   (* execution continues here after CRSWITCH(newcr) *)
END Coroutine;

PROCEDURE Setup;
   VAR
      cr: SYSTEM.COROUTINE;
BEGIN
   Coroutine(crparams, cr);
   (* ... *)
END Setup;
.Pe
.B CRSPAWN
performs following steps:
.IP \(bu
Creation of a new stack.
.br
The initial stack size is determined by the size of the activation record
of the calling procedure
and a default value taken from \fICoroutines.defaultsize\fP.
An optional second parameter of
.B CRSPAWN
allows to specify the initial stack size.
Note that the current storage management system allows coroutine stacks
to grow dynamically.
.IP \(bu
The coroutine structure is allocated at the beginning of the new stack
and initialized.
.br
The coroutine structure contains information about the stack
management registers (pointers to the activation record) and
the program counter of the coroutine.
The program counter of the coroutine is set to the instruction
after the call of
.B CRSPAWN .
.IP \(bu
The activation record (parameters and local variables)
of the procedure calling
.B CRSPAWN
is copied to the new stack.
.IP \(bu
Immediate return to the calling procedure.
Thus, the coroutine procedure must not be a function.
.LP
.B CRSPAWN
returns like
.B CRSWITCH
if the newly created coroutine resumes execution.
.LP
Only a destination parameter is needed for
.B CRSWITCH
because the pointer to the coroutine (of type \fBCOROUTINE\fP)
does not change.
.LP
The pointer to the main coroutine is initialized during runtime
start-off in the system interface module
.I Coroutines :
.Pg
DEFINITION Coroutines;

   (* run time interface to coroutines *)

   IMPORT SYS := SYSTEM;

   TYPE
      Coroutine = SYS.COROUTINE;

      CoroutineTag = POINTER TO CoroutineTagRec;
      CoroutineTagRec = RECORD END;

   VAR
      defaultsize: LONGINT;
         (* default initial stack size of a coroutine,
            the size of the activation record of the procedure
            calling CRSPAWN is added to the default;
            the default is taken if the second parameter of CRSPAWN
            is omitted
         *)
      tag: CoroutineTag; (* used for all coroutines *)

      main: Coroutine;
         (* is allocated during initialisation of this module
            and points to the main coroutine
         *)

      source: Coroutine;
         (* the last CRSWITCH operation was executed by this coroutine *)

      current: Coroutine;
         (* coroutine currently active *)

END Coroutines.
.Pe
.NH
An Example: Simple Producer/Consumer Problem
.LP
.Pg
PROCEDURE Produce(VAR newtoken: Token;
                  VAR producer, consumer: Coroutines.Coroutine);
   VAR
      token: Token;
BEGIN
   SYSTEM.CRSPAWN(producer);
   LOOP
      (* produce token *)
      newtoken := token;
      SYSTEM.CRSWITCH(consumer);
   END;
END Produce;

PROCEDURE Consume(VAR newtoken: Token;
                  VAR producer, consumer: Coroutines.Coroutine);
  VAR
     token: Token;
BEGIN
   SYSTEM.CRSPAWN(consumer);
   LOOP
      token := newtoken;
      (* consume token *)
      SYSTEM.CRSWITCH(producer);
   END;
END Consume;

PROCEDURE Setup;
   VAR
      token: Token;
      producer, consumer: Coroutines.Coroutine;
BEGIN
   Produce(token, producer, consumer);
   Consume(token, producer, consumer);
   SYSTEM.CRSWITCH(producer);
END Setup;
.Pe
.NH
Further Hints
.LP
Ulm's Oberon library offers a couple of abstractions which base on coroutines.
\fITasks\fP introduces the notion of tasks, task groups and schedulers.
Each task group is controlled by a scheduler which is member
of another task group.
Task groups and schedulers form a tree with the main task as root.
.LP
Tasks need not to know about other coroutines and how to call \fBCRSWITCH\fP.
Instead, they may tell what they are waiting for by the use of
conditions, an abstraction offered by \fIConditions\fP.
Specific condition types are exported by \fIStreamConditions\fP,
\fITimeConditions\fP, \fIEventConditions\fP, \fISemaphores\fP
and other modules.
.LP
Usually, schedulers follow the standard scheduler interface of
\fISchedulers\fP which allows to add tasks dynamically.
A simple round robin scheduler without priorities is exported by
\fIRoundRobin\fP.
A first task group which is managed by a round robin scheduler is
created by the default implementation of \fISysModules\fP.
.\" ---------------------------------------------------------------------------
.\" $Id: coroutines,v 1.2 1994/02/15 09:14:48 borchert Exp $
.\" ---------------------------------------------------------------------------
.\" $Log: coroutines,v $
.\" Revision 1.2  1994/02/15  09:14:48  borchert
.\" ``Further Hints'' added
.\" Pg/Pe macros introduced
.\" some typos fixed
.\"
.\" Revision 1.1  1990/08/31  17:02:10  borchert
.\" Initial revision
.\"
.\" ---------------------------------------------------------------------------
