Coroutine Report
Coroutine Report
Keld Helsgaun
E-mail: [email protected]
Abstract
This report describes a portable C++ library for coroutine se-
quencing. The facilities of the library are based on the corou-
tine primitives provided by the programming language
SIMULA. The implementation of the library is described and
examples of its use are given. One of the examples is a library
for process-oriented discrete event simulation.
1. Introduction
Coroutines can be used to describe the solutions of algorithmic problems that
are otherwise hard to describe [1]. Coroutines provide the means to organize
the execution of a program as several sequential processes.
A coroutine is an object that has its own stack of procedure activations. A co-
routine may temporarily suspend its execution and another coroutine may be
executed. A suspended coroutine may later be resumed at the point where it
was suspended.
This form of sequencing is called alternation. Figure 1.1 shows a simple ex-
ample of alternation between two coroutines.
1
coroutine A coroutine B
Resume(B) Resume(A)
Resume(B)
Resume(A)
This report describes a library for coroutine sequencing in C++. The imple-
mentation does not use any platform-specific features and will run unmodi-
fied on most platforms. The facilities of the library are based on the coroutine
primitives provided by the programming language SIMULA [2, 3]. Both
symmetric and semi-symmetric sequencing is supported.
2
2. The coroutine library
This section gives a brief description of the coroutine library from the user's
point of view.
#ifndef Sequencing
#define Sequencing(S) { ...; S; }
class Coroutine {
protected:
virtual void Routine() = 0;
};
Coroutine *CurrentCoroutine();
Coroutine *MainCoroutine();
#endif
Coroutine C resumes its execution from its current execution location, which
normally coincides with the point where it last left off. The current coroutine
is suspended within the Resume or Call operation, which is only com-
pleted at the subsequent resumption.
3
The Call operation establishes the current coroutine as C ’s caller. A subor-
dinate relationship exists between caller and called coroutines. C is said to be
attached to its caller.
The current coroutine can relinquish control to its caller by means of the op-
eration
Detach()
The caller resumes its execution from the point where it last left off. The cur-
rent coroutine is suspended within the Detach operation, which is only
completed at the subsequent resumption.
Below is shown a complete coroutine program. The program shows the use
of the Resume function for coroutine alternation as illustrated in Figure 1.1.
The macro Sequencing is used in the last line to make the main program
behave as a coroutine.
4
#include "coroutine.h"
#include <iostream.h>
void MainProgram() {
A = new CoA;
B = new CoB;
Resume(A);
cout << "STOP ";
}
5
new
Resume
detached resumed
Detach
Call Detach end Routine
end Routine
attached terminated
1) The coroutine is attached. In this case, the coroutine is detached, its exe-
cution is suspended, and execution continues at the reactivation point of
the component to which the coroutine was attached.
6
A call Resume(C) causes the execution of the current operative component
to be suspended and execution to be continued at the reactivation point of C .
The call constitutes an error in the following cases:
C is NULL
C is attached
C is terminated
C is NULL
C is attached
C is resumed
C is terminated
A coroutine program using only Resume and Detach is said to use sym-
metric coroutine sequencing. If only Call and Detach are used, the pro-
gram is said to use semi-symmetric coroutine sequencing. In the latter case,
the coroutines are called semi-coroutines.
7
3. Examples
3. 1 A simple dice game
The following program simulates four people playing a simple dice game.
The players, represented as coroutines, take turns at throwing a die. The first
player to accumulate 100 pips wins and prints his identification.
The Player -objects are kept in a circular list. When a Player -object be-
comes active, it throws the die by selecting a random integer between 1 and
6. If the Player -object has not won, it resumes the next Player -object in
the circle. Otherwise, it terminates, causing the main program to be resumed.
#include "coroutine.h"
#include <stdlib.h>
#include <iostream.h>
void Routine() {
int Sum = 0;
while ((Sum += rand()%6+1) < 100)
Resume(Next);
cout << "The winner is player " << Id << endl;
}
};
8
#include "coroutine.h"
#include <stdlib.h>
#include <iostream.h>
void Routine() {
for (;;) {
Sum += rand()%6+1;
Detach();
}
}
};
9
3.2 Generation of permutations
void Permute(int k) {
if (k == 1)
Detach();
else {
Permute(k-1);
for (int i = 1; i < k; i++) {
int q = p[i]; p[i] = p[k]; p[k] = q;
Permute(k-1);
q = p[i]; p[i] = p[k]; p[k] = q;
}
}
}
void Routine() {
for (int i = 1; i <= N; i++)
p[i] = i;
More = 1;
Permute(N);
More = 0;
}
};
The following program uses class Permuter to print all permutations of the
integers between 1 and 5.
10
void PrintPermutations(int n) {
Permuter *P = new Permuter(n);
Call(P);
while (P->More) {
for (int i = 1; i <= n; i++)
cout << P->p[i] << " ";
cout << endl;
Call(P);
}
}
11
3.3 Text transformation
Consider the following problem [4, 5]. A text is to be read from cards and
printed on a line printer. Each card contains 80 characters, but the line printer
prints 125 characters on each line. We want to pack as many characters as
possible on each output line, marking the transition from one card to the next
merely by the insertion of an extra space. In the text a consecutive pair of as-
terisks is to be replaced by a ‘^ ’. The end of the text is marked by the special
character ‘• ’.
The program given solves this problem by means of the following five co-
routines:
Disassembler takes the characters from the array Card and deliv-
ers them one by one to the squasher (through the
global character variable c1 ).
12
#include "coroutine.h"
#include <iostream.h>
13
class Assembler : public Coroutine {
void Routine() {
for (;;) {
for (int i = 0; i < LineLength; i++) {
Line[i] = c2;
if (c2 == '•') {
while (++i < LineLength)
Line[i] = ' ';
Resume(thePrinter);
Detach(); // back to main program
}
Resume(theSquasher);
}
Resume(thePrinter);
}
}
};
void TextTransformation() {
theReader = new Reader();
theDisassembler = new Disassembler();
theSquasher = new Squasher();
theAssembler = new Assembler();
thePrinter = new Printer();
Resume(theDisassembler);
}
14
3.4 Two simple generators
void RandTest() {
Rand *R = new Rand;
for (int i = 1; i <= 20; i++)
cout << R->Next() << endl;
}
15
3.4.2 A Fibonacci number generator
The Fibonacci numbers are integers defined by the following recurrence rela-
tion
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, ...
Class Fibonacci shown below can be used as a generator for the Fibo-
nacci numbers.
A small test program that prints the first 20 Fibonacci numbers is shown be-
low.
void FibonacciTest() {
Fibonacci *F = new Fibonacci;
for (int i = 1; i <= 20; i++)
cout << F->Next() << endl;
}
16
3.5 Merging two sorted arrays
The following program merges two sorted arrays, A and B , into a single
sorted array, C . A coroutine is associated with each of the two arrays to be
merged. At any moment the coroutine proceeds, which inspects the smallest
data item.
#include "coroutine.h"
#include <iostream.h>
void Routine() {
while (Index < Limit) {
if (Partner->Array[Partner->Index] <
Array[Index])
Resume(Partner);
C[CIndex++] = Array[Index++];
}
while (CIndex < m+n)
C[CIndex++] =
Partner->Array[Partner->Index++];
}
};
void MergeArrays() {
Traverser *X = new Traverser(A, m);
Traverser *Y = new Traverser(B, n);
X->Partner = Y; Y->Partner = X;
C = new int[m+n];
CIndex = 0;
Resume(X);
for (int j = 0; j < m+n; j++)
cout << C[j] << " ";
cout << endl;
}
17
3.6 Merging binary search trees
Class Traverser shown below is intended to be used for scanning the val-
ues in a binary search tree of integers in ascending order.
class Tree {
public:
Tree(int V, Tree *L, Tree *R) :
Value(V), Left(L), Right(R) {}
int Value;
Tree *Left, *Right;
};
void Routine() {
Traverse(MyTree);
Current = INT_MAX;
}
18
An example of use is given in the test program below, which merges the val-
ues of two binary search trees and output the values in ascending order.
void MergeTrees() {
Tree *Tree1 =
new Tree(8,
new Tree(5,
new Tree(1, NULL, NULL),
new Tree(6, NULL, NULL)),
new Tree(10,
NULL,
new Tree(12,
NULL,
new Tree(15,
NULL,
new Tree(17, NULL, NULL)))));
Tree *Tree2 =
new Tree(13,
new Tree(4,
new Tree(2, NULL, NULL),
new Tree(9,
new Tree(7, NULL, NULL),
new Tree(11, NULL, NULL))),
new Tree(20,
new Tree(14,
NULL,
new Tree(18, NULL, NULL)),
new Tree(30, NULL, NULL)));
Traverser *T1 = new Traverser(Tree1);
Traverser *T2 = new Traverser(Tree2);
Call(T1);
Call(T2);
for (;;) {
int min = T1->Current;
if (T2->Current < min) {
min = T2->Current;
Call(T2);
} else {
if (min == INT_MAX) break;
Call(T1);
}
cout << min << " ";
}
cout << endl;
}
19
3.7 Binary insertion sort
The coroutine Tree shown below may be used to sort integer values using
the binary insertion method. Sorting is accomplished by building a binary
search tree. Each node of the tree is a semi-coroutine [6].
If more than one integer is to be sorted, Tree sorts them by creating two
more Tree -objects, Left and Right , and having each of them sort some
of the integers.
Left sorts integers less than or equal to the value in the node, whereas
Right sorts integers larger than the value in the node.
The end of the set of integers to be sorted is signaled with the value -1. When
a coroutine receives a value of -1, it stops sorting and prepares to return the
sorted integers one at a time.
int V;
void Routine() {
if (V == -1) {
Detach();
V = -1;
return;
}
Value = V;
Tree *Left = new Tree, *Right = new Tree;
for (;;) {
Detach();
if (V == -1)
break;
if (V <= Value)
Left->Send(V);
else
Right->Send(V);
}
Left->Send(-1); Right->Send(-1);
Detach();
20
for (;;) {
V = Left->Receive();
if (V == -1)
break;
Detach();
}
V = Value;
Detach();
for (;;) {
V = Right->Receive();
if (V == -1)
break;
Detach();
}
V = -1;
}
public:
void Send(int InputValue) {
V = InputValue;
Call(this);
}
int Receive() {
Call(this);
return V;
}
};
A simple program that uses class Tree to sort 100 random integers between
0 and 100 is shown below.
void BinaryInsertionSort() {
Tree *BST = new Tree;
for (int i = 0; i < 100; i++)
BST->Send(rand()%100);
BST->Send(-1);
int i;
while ((i = BST->Receive()) != -1)
cout << i << " ";
cout << endl;
}
21
3. 8 A cash dispenser
Coroutines may often be used to solve problems that are solvable by means
of backtracking. Each incarnation of a recursive solution of the problem is
replaced by a coroutine.
Consider the following problem [7]. A cash dispenser is able to make pay-
ments to a customer. Write a program that, given a wanted amount of money,
computes the number of coins and notes to be paid out.
Such a program is shown below. Each kind of coin (or note) is represented
as an instance of class Coin . The integer members Denomination and
Number denote the denomination of this kind of coin, and the number of
available coins of this denomination, respectively.
For each kind of coin the program computes the number of that coin to be
used in a payment. The integer member Used contains this number.
Each Coin -object is a coroutine that is able to try all possible payments from
its stock of coins. The objects are held in a two-way list in descending order
of coin denominations. Each time a coroutine becomes active, it tries the next
possible payment and resumes its successor (Suc ). However, if it has no
successor, or all possible payments have been tried, it resumes its predeces-
sor (Pred ). If it has no predecessor, i.e., it is the first object in the list, all
possible combinations have been tried, and the given payment problem can-
not be solved.
The two-way list is implemented by using the facilities of the library simset
(described in Appendix D).
22
#include "coroutine.h"
#include "simset.h"
#include <iostream.h>
int Amount;
inline int min(int a, int b) { return a < b ? a : b; }
void PrintSolution() {
if (Pred() != NULL)
((Coin*) Pred())->PrintSolution();
if (Used > 0)
cout << Used << " of " << Denomination
<< endl;
}
void Routine() {
for (;;) {
for (Used =
min(Amount/Denomination, Number);
Used >= 0;
Used--) {
Number -= Used;
Amount -= Used*Denomination;
if (Amount == 0) {
PrintSolution();
Detach();
}
if (Suc() != NULL)
Resume((Coin*) Suc());
Amount += Used*Denomination;
Number += Used;
}
if (Pred() == NULL) {
cout << "No solution" << endl;
Detach();
}
Resume((Coin*) Pred());
}
}
};
23
void ChangeDispensor() {
cout << "Amount to be paid: "; cin >> Amount;
Head *List = new Head;
(new Coin(1000,19))->Into(List);
(new Coin(500,9))->Into(List);
(new Coin(100,11))->Into(List);
(new Coin(50,10))->Into(List);
(new Coin(20,32))->Into(List);
(new Coin(10,0))->Into(List);
(new Coin(5,1))->Into(List);
(new Coin(1,7))->Into(List);
Resume((Coin*) List->First());
}
24
3. 9 A filter for telegrams
Assume we have a file consisting of a set of telegrams. The end of the file is
signaled by two consecutive ZZZZ-words. Write a program that prints the
contents of the file such that
The program below solves this problem. The program contains the following
three coroutines
LetterProducer reads letters from the file and delivers them one
by one to the word producer. Each new line
character is replaced by a space.
25
#include "coroutine.h"
#include <iostream.h>
#include <fstream.h>
#include <string.h>
26
class Printer : public Coroutine {
void Routine() {
int LineLength = 0;
for (;;) {
while (strcmp(Word, "ZZZZ")) {
if (LineLength + strlen(Word) > 20) {
cout << endl;
LineLength = 0;
}
cout << Word << " ";
LineLength += strlen(Word) + 1;
Resume(theWordProducer);
}
cout << endl << endl;
LineLength = 0;
Resume(theWordProducer);
}
}
};
void TelegramFilter() {
do {
cout << "Enter file name: ";
char FileName[80];
cin >> FileName;
delete TelegramFile;
TelegramFile = new ifstream(FileName,ios::in);
} while (!TelegramFile->is_open());
27
4. Implementation
A coroutine is characterized mainly by its execution state consisting of its cur-
rent execution location and a stack of activation records. The bottom element
of the stack is the Routine activation record. The remaining part of the
stack contains activation records corresponding to function activations trig-
gered by Routine .
The central issue when implementing coroutines is how to achieve such con-
text switches. The goal is to implement the primitive Enter(C) with the
following semantics [1]:
In the following two implementations of Enter are presented. The first im-
plementation is based on copying of stacks in and out of C++'s runtime
stack. In the second implementation all stacks reside in the runtime stack
(i.e., no stacks are copied).
28
4.1 The copy-stack implementation
When a coroutine suspends, the runtime stack and the current execu-
tion location are copied to two buffers (StackBuffer and
Environment ) associated with the coroutine.
The standard C++ functions setjmp and longjmp are used to implement
the context switch.
setjmp saves the current state in the buffer and returns 0. longjmp returns
the processor to a previous state, as though setjmp had returned a value
other than 0.
class Coroutine {
friend void Resume(Coroutine *);
friend void Call(Coroutine *);
friend void Detach();
protected:
Coroutine();
~Coroutine();
virtual void Routine() = 0;
private:
void Enter();
void StoreStack();
void RestoreStack();
char *StackBuffer, *High, *Low;
size_t BufferSize;
jmp_buf Environment;
Coroutine *Caller, *Callee;
};
29
The data members of the class have the following meaning.
BufferSize Copy of
C++'s runtime C++'s runtime
stack stack
StackBuffer
High
Coroutine Low
object BufferSize
Caller
Callee
30
The code of the function Enter is shown below.
void Coroutine::Enter() {
if (!Terminated(Current)) {
Current->StoreStack();
if (setjmp(Current->Environment))
return;
}
Current = this;
if (StackBuffer) {
Routine();
delete Current->StackBuffer;
Current->StackBuffer = 0;
Detach();
}
RestoreStack();
}
The auxiliary function StoreStack is used to save the run time stack. Its
implementation is shown below.
void Coroutine::StoreStack() {
if (!Low) {
if (!StackBottom)
Error("StackBottom is not initialized");
Low = High = StackBottom;
}
char X;
if (&X > StackBottom)
High = &X;
else
Low = &X;
if (High - Low > BufferSize) {
delete StackBuffer;
BufferSize = High - Low;
if (!(StackBuffer = new char[BufferSize]))
Error("No more space available");
}
memcpy(StackBuffer, Low, High - Low);
}
First, the function computes the boundaries of the runtime stack, Low and
High . It is assumed that the bottom of the runtime stack has already been
initialized (by the macro Sequencing ). Next, if necessary, it allocates a
buffer, StackBuffer , to hold a copy of the run time stack. Finally, the run
time stack is copied to this buffer.
Note that the function takes account of the fact that the runtime stack may
grow up on some platforms and down on others.
31
In order to restore the state of a coroutine, the auxiliary function
RestoreStack is used. The code of this function is shown below.
void Coroutine::RestoreStack() {
char X;
if (&X >= Low && &X <= High)
RestoreStack();
Current = this;
memcpy(Low, StackBuffer, High - Low);
longjmp(Current->Environment, 1);
}
The function copies the contents of the stack buffer to the runtime stack, and
jumps to the execution location saved in the buffer Environment . First,
however, the function calls itself recursively as long as the current top ad-
dress is within the saved address bounds of the runtime stack. This prevents
the restored stack from being destroyed by the subsequent call of longjmp .
Resume(Coroutine *Next) {
while (Next->Callee)
Next = Next->Callee;
Next->Enter();
}
void Detach() {
Coroutine *Next = Current->Caller;
if (Next)
Current->Caller = Next->Callee = 0;
else {
Next = &Main;
while (Next->Callee)
Next = Next->Callee;
}
Next->Enter();
}
32
The complete program code of this version of the coroutine library may be
found in Appendix B.
The implementation is based on the same principles as was used by the author
in his implementation of a library for backtrack programming in C [8]. Actu-
ally, the latter library may easily be implemented by means of the coroutine
library (see Section 7). The same principles were used in [9] to extend C++
with control extensions similar to those described in this report.
33
4.2 The share-stack implementation
This implementation is more complex than the previous one. The basic idea,
first time described by Kofoed [10], is to let all coroutine stacks share C++'s
runtime stack.
The runtime stack is divided into contiguous areas of varying size. An area is
either unused or contains a coroutine stack. Recursive function calls are used
to wind down the stack and mark off allocated areas.
Each area contains a control block, called a task, which describes the prop-
erties of the area, for example its size and whether or not it is in use.
class Coroutine {
friend void Resume(Coroutine *);
friend void Call(Coroutine *);
friend void Detach();
friend void InitSequencing(size_t main_stack_size
= DEFAULT_STACK_SIZE);
protected:
Coroutine(size_t stack_size = DEFAULT_STACK_SIZE);
virtual void Routine() = 0;
private:
void Enter();
void Eat();
Task *MyTask;
size_t StackSize;
int Ready, Terminated;
Coroutine *Caller, *Callee;
};
34
The structure Task is shown below.
struct Task {
Coroutine *MyCoroutine;
jmp_buf jmpb;
int used;
size_t size;
struct Task *pred, *suc;
struct Task *prev, *next;
};
35
MyCoroutine
jmpb
Task
MyTask object
used = 1
StackSize
Coroutine
Ready = 0 pred
object
Terminated = 0 suc
StackSize
Caller prev
Callee next
All control blocks are linked together with pointers (pred and suc ) in a doubly
linked list. When a new stack is to be allocated, the linked list is searched, using a
first-fit algorithm, for a free area that is large enough. If the requested stack size is
smaller than the area found, and the are is large enough to contain another stack of
a predefined minimum size, the area is split using the previously mentioned
recursive function Eat , creating a new free area. The original block is marked as
"used" and is ready to be used for executing the actions of the coroutine in
question.
36
When a stack is no longer needed (because the corresponding coroutine termi-
nates), the control block is marked "free" and possibly merged with the preceding
or following free block (referenced by prev and next , respectively).
Second, in order to speed up the search for a possible merge of free blocks, the
singly linked list of adjacent blocks has been replaced by a doubly linked list.
Third, at program initialization the user does not need to provide the size of the
total stack area (only the size of the stack area required for the main program).
37
4.3 Comparison of the two implementations
An advantage of the copy-stack implementation is that the user does not need
to bother about stack sizes for coroutines.
4.3.2 Efficiency
In general, the time used for context switching is reduced when the copy-
stack version of the library is replaced by the share-stack version. This gain
in speed may be of importance in applications with many context switches
and/or large stacks.
38
However, the restriction is usually not important. Actually, as will be shown
in Section 7 the backtracking property of the copy-stack implementation may
be exploited for writing applications that combine the use of coroutine se-
quencing with backtracking.
4.3.4 Robustness
4.3.6 Maintenance
4.3.7 Portability
Both implementations are portable. At the time of writing, they have both
been installed and tested successfully with compilers on the following ma-
chines: Macintosh, IBM PC and Sun SPARC.
39
5. The simulation library
Discrete event simulation is an important application area for coroutine se-
quencing.
Processes may have active as well as inactive phases. A process may be sus-
pended temporarily and resumed later from where it left off. Thus, a process
has the properties of a coroutine.
The coroutine library described in this report can be used to implement a li-
brary for simulation. Such a library is described in this section. The design of
the library follows very closely the design of the built-in package for discrete
event simulation in SIMULA, class simulation [3].
The currently active process always has the smallest event time associated
with it. This time, the simulation time, moves in jumps to the event time of
the next scheduled process.
Scheduled processes are contained in an event list. The processes are ordered
in accordance with increasing event times. The process at the front of the
event list is always the one, which is active. Processes not in the event list are
either terminated or passive.
40
At any point in simulation time, a process can be in one (and only one) of the
following states:
active: the process is at the front of the event list. Its actions are being
executed
suspended: the process is in the event list, but not at the front
passive: the process is not in the event list and has further actions to exe-
cute
terminated: the process is not in the event list and has no further actions
to execute.
#ifndef Simulation
#define Simulation Sequencing
#include "coroutine.h"
#include "simset.h"
#include “random.h”
Process *Main();
Process *Current();
double Time();
41
void Reactivate(Process *P);
void Reactivate(Process *P, Haste H, double T);
void Reactivate(Process *P, Haste H, double T,
Prior Prio);
void Reactivate(Process *P1, Ranking Rank,
Process *P2);
#endif
Since class Process is a subclass of class Link , each process has the ca-
pability of being a member of a two-way list (see Appendix D). This is use-
ful, for example, when processes must wait in a queue.
42
There are seven ways to activate a currently passive process:
43
The following four public member functions are available in class Process :
Idle() returns 1 if the process is not currently in the event list. Oth-
erwise 0 .
EvTime() returns the time at which the process is scheduled for ac-
tivation.
In addition, the simulation library provides the function Accum that can be
used to compute the time integral of a variable.
Accum(double &A, double &B, double &C, double
D) accumulates the “time integral” of the variable C , interpreted as a
step function of the simulated time. The integral is accumulated in the
variable A . The variable B contains the event time at which the vari-
ables were last updated. The value of D is the current increment of the
step function. The code is:
A += C*(Time()-B);
B = Time();
C += D;
A listing of the complete source code of the simulation library can be found in
Appendix E
44
6. A simulation example
This example has been taken from [2]
A garage owner has installed an automatic car wash that services cars one at a
time. Each service takes 10 minutes. When a car arrives, it goes straight into
the car wash if this is idle, otherwise it must wait in a queue. As long as cars
are waiting, the car wash is in continuous operation serving on a first-come,
first-served basis. The average time between car arrivals has been estimated at
11 minutes.
The garage owner is interested in predicting the maximum queue length and
average waiting time if he installs one more car wash.
It is assumed that each car wash is manned by a car washer, and that the car
washers start their day in a tearoom and return there each time they have no
work to do. A car washer may described by the following subclass of
Process :
The actions of a car washer are contained in an infinite loop (the length of the
simulation is supposed to be determined by the main program). Each time a
car washer is activated, he leaves the tearoom (by calling Out ) and starts
serving the cars in the waiting line. He takes the first car out of the waiting
line, washes it for ten minutes (Hold(10) ). The car washer will continue
servicing, as long as there are cars waiting in the queue. If the waiting line
becomes empty, he returns to the tearoom and waits.
45
A car may be described by the following subclass of Process :
On arrival each car enters the waiting line and, if the tearoom is not empty, it
activates the idle car washer in the room. The car washer then washes it. If,
however, the tearoom is empty, the car waits until a car washer can service it.
When a car has been washed (and activated by the car washer), it leaves the
garage.
The following subclass of Process is used to make cars arrive to the garage
with an inter-arrival time of P minutes:
46
The main program, shown below, generates the two queues, TeaRoom and
WaitingLine , and activates the two car washers and the car generator.
void CarWash() {
P = 11.0; N = 2; SimPeriod = 200; U = 5;
TeaRoom = new Head;
WaitingLine = new Head;
for (int i = 1; i <= N; i++)
(new CarWasher)->Into(TeaRoom);
Activate(new CarGen);
Hold(SimPeriod+100000);
Report();
}
Simperiod denotes the total opening time of the garage (200 minutes). All
cars that have arrived before the garage closes down are washed. When all
activity has stopped, function Report , shown below, prints the number of
cars washed, the average elapsed time (wait time plus service time), and the
maximum queue length.
void Report() {
cout << N << " Car washer simulation\n";
cout << "No.of cars through the system = "
<< NoOfCustomers << endl;
cout << "Av.elapsed time = "
<< ThroughTime/NoOfCustomers << endl;
cout << "Maximum queue length = " << MaxLength
<< endl;
}
This program was used with Simperiod set to 1000000 to compare the
efficiency of the two versions of the coroutine library.
The CPU time used to run the program on a 300 MHz PowerPC Macintosh
was
The last line shows the time needed to run a SIMULA version of the program
(Lund SIMULA 4.07).
47
The CPU-time used to run the program on a SUN SPARC server was
The last line shows the time needed to run a SIMULA version of the program
(cim-2.8, a SIMULA compiler that produces C code).
As can be seen, the share-stack is more efficient than the copy-stack version.
48
7. Combining coroutines and backtracking
Backtrack programming is a well-known technique for solving combinatorial
search problems [11]. The search is organized as a multi-stage search process
where, at each stage, a choice among a number of alternatives has to be
made. Whenever it is found that the previous choices cannot possibly lead to
a solution, the algorithm backtracks, that is to say, re-establishes its state ex-
actly as is was at the most recent choice point and chooses the next untried
alternative at this point. If all alternatives have been tried, the algorithm back-
tracks to the previous choice point.
However, writing programs that explicitly handle their own backtracking can
be difficult, tedious and error-prone. For this reason, a number of high-level
languages have been supplemented with special facilities for backtrack pro-
gramming.
49
Given the copy-stack version of the coroutine library, it is a simple matter to
implement CBack. Appendix H contains the source code of such an imple-
mentation. It should be noted that only the most essential parts of CBack have
been included in this implementation.
The program below solves the classical 'eight-queens' problem using the
CBack library. The task is to place eight queens on a chessboard so that no
queen is under attack by another; that is, there is at most one queen on each
row, column and diagonal.
#include "CBack.h"
#include <iostream.h>
int Q[9];
void EightQueensProblem() {
for (int r = 1; r <= 8; r++) {
int c = Choice(8);
for (int i = 1; i < r; i++)
if (c == Q[i] || abs(c - Q[i]) == r - i)
Backtrack();
Q[r] = c;
}
for (int r = 1; r <= 8; r++)
cout << Q[r] << " ";
cout << endl;
}
50
Depth-first is the default search strategy. If required, the user may obtain
best-first strategy. In the present implementation the simple sorted list rep-
resentation of the priority queue of states has been replaced by a pairing heap
representation.
Problem: Given two n-ary trees with non-negative integer node weights, de-
termine which tree has the root to terminal path with the smallest node weight
sum (if both trees possesses such a path, then either tree is an admissible an-
swer).
51
#include "CBack.h"
#include <iostream.h>
#include <limits.h>
void Routine() {
unsigned long Sum; // current path sum
unsigned int NodeNow; // current node pointer
void Treewalk() {
// assume Desc, Deg, Wt, Root1 and Root2 already read in
Shortest *T[] = {new Shortest(Root1), new Shortest(Root2)};
unsigned long OldBest;
int i = 0; // let system 0 try first
do {
OldBest = Best; // save current best path weight
Call(T[i]); // call current subsystem
i = 1 - i; // switch to other subsystem
} while (Best < OldBest);
cout << "Tree " << i << " has minimum terminal path"
<< endl;
}
52
7.2 Context-free language intersection
• Set k = 1.
• Generate all length k strings of L(G1 ) via a backtracking system. As each
new character is produced (in left-to-right order) in a potential length k
member of L(G1 ), pass that character to a G2 parser, operating as a back-
tracking subsystem of the L(G1 ) generator.
• The G2 parser attempts to accommodate the current string as extended by
the new character. If the G2 parser fails in this attempt, it signals failure to
the G1 generator, which then retracts that character. In either case, the G1
generator continues its generation process as described in step 2.
• This process continues until either (a) an entire string of length k is pro-
duced and parsed as a member of L(G2 ), in which case a success is re-
ported and the program terminates after printing the string; or (b) the G1
generator has produced all length k members of L(G1 ).
• In the latter case k is incremented and compared against lim. If k > lim, a
message is printed indicating the absence of the desired string and the
program halts. Otherwise, the process begins anew at step 2.
A program that implements this algorithm is shown below. For details see
[12].
53
#include "CBack.h"
#include <iostream.h>
void parseboss::Routine() {
Notify(parseptr); // backtrace parseptr
parseptr = 0; // initialize string pointer
parse(root2); // start up parser
if (parseptr < genref->genptr+1) // parsed only prefix of string
Backtrack(); // so backup
ok = 1; // signal success on last character
strfound = 1; // and overall success
Detach(); // await generator's pleasure
strfound = 0; // another character has arrived
Backtrack(); // so backup
}
54
void parseboss::parse(char goal) {
// find string spanned by goal, starting at parseptr
switch(ruletype[goal]) {
case alt:
parse(rule[goal][Choice(2)-1]);
break;
case conc:
parse(rule[goal][0]);
parse(rule[goal][1]);
break;
case term:
if (parseptr > genref->genptr) { // need another character
ok = 1; // so far, so good
Detach(); // if controls returns, we have it
}
if (str[parseptr] != rule[goal][0])
Backtrack();
parseptr++;
}
}
void genboss::Routine() {
Notify(genptr); // backtrace genptr
genptr = 0; // initialize string pointer
generate(root1,k); // start up generation
if (!strfound) // generator finished, but not parser
Backtrack(); // so back up
}
55
void GrammarIntersection() {
// assume rule, ruletype, root1, root2 and lim already read in
BreadthFirst = 1; // use breadth-first search
strfound = 0;
int k = 0;
while (k < lim && !strfound) {
// see if a common string of length k+1 exists
k++;
genboss *g = new genboss(k);
Call(g);
delete g;
ClearAll();
}
if (strfound) {
cout << "Success, string = ";
for (int i = 0; i < k; i++)
cout << str[i];
cout << endl;
}
else
cout << "No common string of length less than or equal to "
<< lim << endl;
}
56
8. Conclusions
This report describes a portable C++ library for coroutine sequencing. The
library has been implemented in two versions, the copy-stack version and the
share-stack version. Both versions show quite good performance. Their ade-
quacy has been demonstrated through the implementation of a library for
process-oriented discrete event simulation.
57
References
1. C. D. Marlin,
Coroutines,
Lecture Notes in Computer Science (1980).
3. Programspråk – SIMULA,
SIS, Svensk Standard SS 63 61 14 (1987).
4. M. E. Conway,
Design of a Separable Transition-Diagram Compiler,
Comm, A.C.M., 6(7), pp. 396-408 (1963).
7. H. B. Hansen,
SIMULA - et objektorienteret programmeringssprog,
Kompendium, Roskilde Universitetscenter (1990).
8. K. Helsgaun,
CBack: A Simple Tool for Backtrack Programming in C,
Softw. prac. exp., 25(8), pp. 905-934 (1995).
9. L. Nigro,
Control extensions in C++,
Object Oriented Programming, 6(9), pp. 37-47 (1994)
58
10. S. Kofoed,
Portable Multitasking in C++,
Dr. Dobb’s Journal, November (1995)
11. R. W. Floyd,
Nondeterministic algorithms.
Journal ACM ,14(4), 636-644 (1967).
12. G. Lindstrom,
Backtracking in a Generalized Control Setting,
A.C.M. Trans. on Programming Languages and Systems, 1(1),
pp. 8-26 (1979).
59
Appendices
60
A. Installation of the coroutine library
The source code of the coroutine library is provided in two files, a header file
called coroutine.h , and a source file called coroutine.cpp . Two ver-
sions of the library are provided, the copy-stack version (described in Section
4.1) and the share-stack version (described in Section 4.2).
The library may now be compiled and tested with the program shown on the
next page [7].
and stop. If any other character is typed, the program should print
m4c3m5
and stop.
A-1
#include "coroutine.h"
#include <iostream.h>
#include <stdlib.h>
void A::Routine() {
cout << "a1"; Detach();
cout << "a2"; Call(c1 = new C);
cout << "a3"; Call(b1);
cout << "a4"; Detach();
}
void B::Routine() {
cout << "b1"; Detach();
cout << "b2"; Resume(c1);
cout << "b3";
};
void C::Routine() {
cout << "c1"; Detach();
cout << "c2" << endl << "==> "; flush(cout);
char command;
cin >> command;
if (command == 'r')
Resume(a1);
else if (command == 'c')
Call(a1);
else
Detach();
cout << "c3"; Detach();
cout << "c4";
}
void TestProgram() {
cout << "m1"; Call(a1 = new A);
cout << "m2"; Call(b1 = new B);
cout << "m3"; Resume(a1);
cout << "m4"; Resume(c1);
cout << "m5" << endl;
}
A-2
B. Source code of the copy-stack version of the
coroutine library
Header file: coroutine.h
#ifndef Sequencing
#define Sequencing(S) {char Dummy; StackBottom = &Dummy; S;}
#include <stddef.h>
#include <setjmp.h>
class Coroutine {
friend void Resume(Coroutine *);
friend void Call(Coroutine *);
friend void Detach();
friend class Process;
friend unsigned long Choice(long);
friend void Backtrack();
protected:
Coroutine(size_t Dummy = 0);
~Coroutine();
virtual void Routine() = 0;
private:
void Enter();
void StoreStack();
void RestoreStack();
char *StackBuffer, *Low, *High;
size_t BufferSize;
jmp_buf Environment;
Coroutine *Caller, *Callee;
static Coroutine *ToBeResumed;
};
Coroutine *CurrentCoroutine();
Coroutine *MainCoroutine();
#define DEFAULT_STACK_SIZE 0
#endif
B-1
Source file: coroutine.cpp
#define Synchronize // {jmp_buf E; if (!setjmp(E))
longjmp(E,1);}
#include "coroutine.h"
#include <iostream.h>
#include <stdlib.h>
#include <string.h>
char *StackBottom;
Coroutine *Coroutine::ToBeResumed = 0;
Coroutine::Coroutine(size_t Dummy = 0) {
char X;
if (StackBottom)
if (&X < StackBottom ?
&X <= (char*) this && (char*) this <= StackBottom :
&X >= (char*) this && (char*) this >= StackBottom)
Error("Attempt to allocate a Coroutine on the stack");
StackBuffer = 0; Low = High = 0; BufferSize = Dummy = 0;
Callee = Caller = 0;
}
Coroutine::~Coroutine() {
delete StackBuffer; StackBuffer = 0;
}
B-2
inline void Coroutine::StoreStack() {
if (!Low) {
if (!StackBottom)
Error("StackBottom is not initialized");
Low = High = StackBottom;
}
char X;
if (&X > StackBottom)
High = &X;
else
Low = &X;
if (High - Low > BufferSize) {
delete StackBuffer;
BufferSize = High - Low;
if (!(StackBuffer = new char[BufferSize]))
Error("No more space available");
}
Synchronize;
memcpy(StackBuffer, Low, High - Low);
}
B-3
void Resume(Coroutine *Next) {
if (!Next)
Error("Attempt to Resume a non-existing Coroutine");
if (Next == Current)
return;
if (Terminated(Next))
Error("Attempt to Resume a terminated Coroutine");
if (Next->Caller)
Error("Attempt to Resume an attached Coroutine");
while (Next->Callee)
Next = Next->Callee;
Next->Enter();
}
void Detach() {
Next = Current->Caller;
if (Next)
Current->Caller = Next->Callee = 0;
else {
Next = &Main;
while (Next->Callee)
Next = Next->Callee;
}
Next->Enter();
}
B-4
C. Source code of the share-stack version of the
coroutine library
Header file: coroutine.h
#ifndef Sequencing
#include <stddef.h>
#include <setjmp.h>
#define DEFAULT_STACK_SIZE 10000
#define Sequencing(S) { InitSequencing(DEFAULT_STACK_SIZE); S; }
class Task;
class Coroutine {
friend void Resume(Coroutine *);
friend void Call(Coroutine *);
friend void Detach();
friend class Process;
friend void InitSequencing(size_t main_StackSize);
protected:
Coroutine(size_t StackSize = DEFAULT_STACK_SIZE);
virtual void Routine() = 0;
private:
void Enter();
void Eat();
Task *MyTask;
size_t StackSize;
int Ready, Terminated;
Coroutine *Caller, *Callee;
static Coroutine *ToBeResumed;
};
Coroutine *CurrentCoroutine();
Coroutine *MainCoroutine();
C-1
struct Task {
Coroutine *MyCoroutine;
jmp_buf jmpb;
int used;
size_t size;
struct Task *pred, *suc;
struct Task *prev, *next;
};
#endif
C-2
Source file: coroutine.cpp
#include "coroutine.h"
#include <iostream.h>
#include <stdlib.h>
#include <limits.h>
#define MIN_STACK_SIZE 500 // minimum stack size
Coroutine *Coroutine::ToBeResumed = 0;
Coroutine::Coroutine(size_t s = DEFAULT_STACK_SIZE) {
Caller = Callee = 0; Ready = 1; Terminated = 0;
StackSize = s;
}
C-3
void Coroutine::Enter() {
if (!Current)
Error("InitSequencing has not been called");
if (Ready) { // find free block
for (MyTask = main_task.suc;
MyTask != &main_task;
MyTask = MyTask->suc)
if (MyTask->size >= StackSize + MIN_STACK_SIZE)
break;
if (MyTask == &main_task)
Error("No more space available\n");
MyTask->MyCoroutine = this;
if (!setjmp(tmp_jmpb))
longjmp(MyTask->jmpb, 1);
Ready = 0;
}
if (!setjmp(Current->MyTask->jmpb)) { // activate control block
Current = this;
longjmp(MyTask->jmpb, 1);
}
}
void Coroutine::Eat() {
static size_t d;
static Task *p;
Task t;
// eat stack
if ((d = labs((char *) &t - (char *) MyTask)) <
StackSize)
Eat();
t.size = MyTask->size - d; //set size
MyTask->size = d;
t.used = 0;
t.suc = main_task.suc;
t.pred = &main_task;
t.suc->pred = main_task.suc = &t;
if (MyTask->next != &t) {
t.next = MyTask->next; // set link pointers
MyTask->next = &t;
t.prev = MyTask;
if (t.next)
t.next->prev = &t;
}
if (!setjmp(t.jmpb)) // wait
longjmp(MyTask->jmpb, 1);
C-4
for (;;) {
// test size
if (StackSize + MIN_STACK_SIZE < t.size &&
!setjmp(t.jmpb))
t.MyCoroutine->Eat(); // split block
t.used = 1; // mark as used
t.pred->suc = t.suc;
t.suc->pred = t.pred;
if (!setjmp(t.jmpb)) // wait
longjmp(tmp_jmpb, 1);
t.MyCoroutine->Routine(); // execute Routine
t.MyCoroutine->Terminated = 1;
t.used = 0; // mark as free
p = t.next;
if (p && !p->used) { // merge with following block
t.size += p->size;
t.next = p->next;
if (t.next)
t.next->prev = &t;
p->pred->suc = p->suc;
p->suc->pred = p->pred;
}
p = t.prev;
if (!p->used) { // merge with preceding block
p->size += t.size;
p->next = t.next;
if (t.next)
t.next->prev = p;
}
else {
t.suc = main_task.suc;
t.pred = &main_task;
t.suc->pred = main_task.suc = &t;
}
if (!setjmp(t.jmpb)) { // save state
if (ToBeResumed) {
static Coroutine *Next;
Next = ToBeResumed;
ToBeResumed = 0;
Resume(Next);
}
else
Detach();
}
}
}
C-5
void Resume(Coroutine *Next) {
if (!Next)
Error("Attempt to Resume a non-existing Coroutine");
if (Next == Current)
return;
if (Next->Terminated)
Error("Attempt to Resume a terminated Coroutine");
if (Next->Caller)
Error("Attempt to Resume an attached Coroutine");
while (Next->Callee)
Next = Next->Callee;
Next->Enter();
}
void Detach() {
Coroutine *Next = Current->Caller;
if (Next)
Current->Caller = Next->Callee = 0;
else {
Next = &Main;
while (Next->Callee)
Next = Next->Callee;
}
Next->Enter();
}
C-6
void InitSequencing(size_t main_StackSize) {
Task tmp;
tmp.size = ULONG_MAX;
Main.StackSize = main_StackSize;
tmp.next = 0;
Main.MyTask = &tmp;
main_task.pred = main_task.suc = &main_task;
tmp.MyCoroutine = Current = &Main;
if (!setjmp(tmp.jmpb))
Main.Eat();
tmp.pred = main_task.pred;
tmp.suc = main_task.suc;
main_task = tmp;
main_task.next->prev = &main_task;
Main.MyTask = &main_task;
main_task.used = 1;
Main.Ready = 0;
}
C-7
D. The simset library
This library contains facilities for the manipulation of two-way linked lists. Its func-
tionality corresponds closely to SIMULA's built-in class simset .
The class Linkage is a common base class for list heads and list members.
The three classes are described below by means of the following variables:
Head *HD;
Link *LK;
Linkage *LG;
Class Linkage
class Linkage {
public:
Link *Pred();
Link *Suc();
Linkage *Prev();
};
D-1
Class Head
HD.First() returns a reference to the first member of the list (0, if the
list is empty).
HD.Last() returns a reference to the last member of the list (0, if the
list is empty).
HD.Cardinal() returns the number of members in the list (0, if the list is
empty).
HD.Empty() returns 1 if the list HD has no members; otherwise 0.
HD.Clear() removes all members from the list.
D-2
Class Link
class Link : public Linkage {
public:
void Out(void);
void Into(Head *H);
void Precede(Linkage *L);
void Follow(Linkage *L);
};
D-3
Header file: simset.h
#ifndef SIMSET_H
#define SIMSET_H
class Linkage {
friend class Link;
friend class Head;
public:
Linkage();
Link *Pred() const;
Link *Suc() const;
Linkage *Prev() const;
private:
Linkage *PRED, *SUC;
virtual Link *LINK() = 0;
};
#endif
D-4
Source file: simset.cpp
#include "simset.h"
void Link::Out() {
if (SUC) { SUC->PRED = PRED; PRED->SUC = SUC; PRED = SUC = 0; }
}
D-5
E. Source code of the simulation library
Header file: simulation.h
#ifndef Simulation
#define Simulation Sequencing
#include "coroutine.h"
#include "simset.h"
#include "random.h"
Process *Main();
Process *Current();
double Time();
E-1
void Activate(Process *P);
void Activate(Process *P, Haste H, double T);
void Activate(Process *P, Haste H, double T, Prior Prio);
void Activate(Process *P1, Ranking Rank, Process *P2);
#endif
E-2
Source file: simulation.cpp
#include "simulation.h"
#include <iostream.h>
void Process::Routine() {
Actions();
TERMINATED = 1;
SQS.UNSCHEDULE(this);
if (SQS.SUC == &SQS)
Error("SQS is empty");
ToBeResumed = SQS.SUC;
}
int Process::Idle() const { return SUC == 0; }
int Process::Terminated() const { return TERMINATED; }
double Process::EvTime() const {
if (SUC == 0)
Error("No EvTime for Idle Process");
return EVTIME;
}
E-3
Process* Process::NextEv() const
{ return SUC == &SQS ? 0 : SUC; }
Process *Main() { return &MainProgram; }
Process *Current() { return SQS.SUC; }
double Time() { return SQS.SUC->EVTIME; }
void Hold(double T) {
Process *Q = SQS.SUC;
if (T > 0)
Q->EVTIME += T;
T = Q->EVTIME;
if (Q->SUC != &SQS && Q->SUC->EVTIME <= T) {
SQS.UNSCHEDULE(Q);
Process *P = SQS.PRED;
while (P->EVTIME > T)
P = P->PRED;
SQS.SCHEDULE(Q,P);
Resume(SQS.SUC);
}
}
void Passivate() {
Process *CURRENT = SQS.SUC;
SQS.UNSCHEDULE(CURRENT);
if (SQS.SUC == &SQS)
Error("SQS is empty");
Resume(SQS.SUC);
}
E-4
enum {direct = 0};
E-5
void Activate(Process *P)
{ Activat(0,P,direct,0,0,0); }
void Activate(Process *P, Haste H, double T)
{ Activat(0,P,H,T,0,0); }
void Activate(Process *P, Haste H, double T, Prior Pri)
{ Activat(0,P,H,T,0,Pri); }
void Activate(Process *P1, Ranking Rank, Process *P2)
{ Activat(0,P1,Rank,0,P2,0); }
E-6
F. The random drawing library
Each of the functions in this library performs a random drawing of some
kind. Their semantics is the same as in SIMULA.
The value is one of the integers A , A +1, …, B -1, B with equal prob-
ability. If B < A , the call constitutes an error.
double Uniform(double A, double B, long &U);
The value is a drawing form the Erlang distribution with mean 1/A
and standard deviation 1/(A * √B) . Both A and B must be positive.
F-1
long Discrete(double A[], long N, long &U);
0 ≤ Discrete(A, U) ≤ N
The value is an integer in the range [0;N -1] where N is the number of
elements in the one-dimensional array A . The latter is interpreted as a
histogram defining the relative frequencies of the values.
F-2
Header file: random.h
#ifndef RANDOM_H
#define RANDOM_H
#endif
F-3
Source file: random.cpp
#include "random.h"
#include <iostream.h>
#include <limits.h>
#include <math.h>
#define p0 (-0.322232431088)
#define p1 (-1)
#define p2 (-0.342242088547)
#define p3 (-0.0204231210245)
#define p4 (-0.0000453642210148)
#define q0 0.099348462606
#define q1 0.588581570495
#define q2 0.531103462366
#define q3 0.10353775285
#define q4 0.0038560700634
F-4
double Normal(double A, double B, long &U) {
double y, x, p, R = Random;
p = R > 0.5 ? 1.0 - R : R;
y = sqrt (-log (p * p));
x = y + ((((y * p4 + p3) * y + p2) * y + p1) * y + p0)/
((((y * q4 + q3) * y + q2) * y + q1) * y + q0);
if (R < 0.5)
x = -x;
return B * x + A;
}
F-5
double Linear(double A[], long B[], long N, long &U) {
if (A[0] != 0.0 || A[N-1] != 1.0)
Error("Linear: Illegal value in first array");
double Basic = Random;
long i;
for (i = 1; i < N; i++)
if (A[i] >= Basic)
break;
double D = A[i] - A[i-1];
if (D == 0.0)
return B[i-1];
return B[i-1] + (B[i]-B[i-1])*(Basic-A[i-1])/D;
}
F-6
G. Source code of the car wash simulation program
#include "simulation.h"
#include <iostream.h>
G-1
void Report() {
cout << N << " Car washer simulation\n";
cout << "No.of cars through the system = "
<< NoOfCustomers << endl;
cout << "Av.elapsed time = " << ThroughTime/NoOfCustomers
<< endl;
cout << "Maximum queue length = " << MaxLength << endl;
}
void CarWash() {
P = 11; N = 2; SimPeriod = 200; U = 5;
TeaRoom = new Head;
WaitingLine = new Head;
for (int i = 1; i <= N; i++)
(new CarWasher)->Into(TeaRoom);
Activate(new CarGen);
Hold(SimPeriod + 10000000);
Report();
}
G-2
H. A rudimentary implementation of CBack
Header file: CBack.h
#ifndef Backtracking
#define Backtracking Sequencing
#include "coroutine.h"
#endif
H-1
Source file: CBack.cpp
#include "CBack.h"
#include <stdlib.h>
#include <iostream.h>
#include <setjmp.h>
void (*Fiasco) () = 0;
void Backtrack() {
if (!TopState) {
if (Fiasco)
Fiasco();
exit(0);
}
TopState->RestoreStack();
}
H-2
I. A complete implementation of CBack (with corou-
tine sequencing)
Header file: Coroutine.h
#ifndef Sequencing
#define Sequencing(S) {char Dummy; StackBottom = &Dummy; S;}
#include <stddef.h>
#include <setjmp.h>
extern char *StackBottom;
extern void (*CleanUp) ();
class Coroutine {
friend void Resume(Coroutine *);
friend void Call(Coroutine *);
friend void Detach();
friend unsigned long Choice(long);
friend void Backtrack();
friend unsigned long NextChoice();
friend class Process;
friend class State;
friend class Notification;
friend void Clean();
protected:
Coroutine();
~Coroutine();
virtual void Routine() = 0;
private:
void Enter();
void StoreStack();
void RestoreStack();
char *StackBuffer, *Low, *High;
size_t BufferSize;
jmp_buf Environment;
Coroutine *Caller, *Callee;
static Coroutine *ToBeResumed;
State *TopState;
unsigned long LastChoice, Alternatives;
long Merit;
};
#endif
I-1
Header file: CBack.h
#ifndef Backtracking
#define Backtracking Sequencing
#include "coroutine.h"
#include <stddef.h>
#endif
I-2
Source file: Coroutine.cpp
#define Synchronize // {jmp_buf E; if (!setjmp(E))
longjmp(E,1);}
#include "coroutine.h"
#include <iostream.h>
#include <stdlib.h>
#include <string.h>
char *StackBottom;
void NoClean() {}
void (*CleanUp) () = NoClean;
class State;
class Notification;
State *TopState;
size_t NotifiedSpace;
Notification *FirstNotification, *N;
unsigned long LastChoice, Alternatives;
long Merit;
Coroutine *Coroutine::ToBeResumed = 0;
I-3
Coroutine::Coroutine() {
char X;
if (StackBottom)
if (&X < StackBottom ?
&X <= (char*) this && (char*) this <= StackBottom :
&X >= (char*) this && (char*) this >= StackBottom)
Error("Attempt to allocate a Coroutine on the stack");
StackBuffer = 0; Low = High = 0; BufferSize = 0;
Callee = Caller = 0; TopState = 0;
LastChoice = Alternatives = 0;
Merit = 0;
}
Coroutine::~Coroutine() {
CleanUp();
delete StackBuffer;
StackBuffer = 0;
}
I-4
inline void Coroutine::Enter() {
if (!Terminated(Current)) {
Current->StoreStack();
if (setjmp(Current->Environment))
return;
}
Current->TopState = ::TopState;
Current->LastChoice = ::LastChoice;
Current->Alternatives = ::Alternatives;
Current->Merit = ::Merit;
::TopState = TopState;
::LastChoice = LastChoice;
::Alternatives = Alternatives;
::Merit = Merit;
Current = this;
if (!StackBuffer) {
Routine();
CleanUp();
delete Current->StackBuffer;
Current->StackBuffer = 0;
if (ToBeResumed) {
Next = ToBeResumed;
ToBeResumed = 0;
Resume(Next);
}
Detach();
}
RestoreStack();
}
I-5
void Call(Coroutine *Next) {
if (!Next)
Error("Attempt to Call a non-existing Coroutine");
if (Terminated(Next))
Error("Attempt to Call a terminated Coroutine");
if (Next->Caller)
Error("Attempt to Call an attached Coroutine");
Current->Callee = Next;
Next->Caller = Current;
while (Next->Callee)
Next = Next->Callee;
if (Next == Current)
Error("Attempt to Call an operating Coroutine");
Next->Enter();
}
void Detach() {
Next = Current->Caller;
if (Next)
Current->Caller = Next->Callee = 0;
else {
Next = &Main;
while (Next->Callee)
Next = Next->Callee;
}
Next->Enter();
}
I-6
Source file: CBack.cpp
#include "CBack.h"
#include <iostream.h>
#include <setjmp.h>
void (*Fiasco) () = 0;
int BreadthFirst = 0;
void Clean() {
State *OldTopState = TopState;
TopState = CurrentCoroutine()->TopState;
ClearChoices();
if (CurrentCoroutine() != MainCoroutine())
TopState = OldTopState;
CurrentCoroutine()->TopState = 0;
}
I-7
class Notification {
public:
void *Base;
size_t Size;
Notification *Next;
};
I-8
unsigned long Choice(long N) {
if (N <= 0) Backtrack();
if (N == 1 && (!TopState || TopState->Merit <= Merit))
return (LastChoice = Alternatives = 1);
State *NewState;
if (FirstFree) {
NewState = FirstFree;
FirstFree = NewState->Next;
}
else if (!(NewState = new State))
Error("No more space for Choice\n");
NewState->LastChoice = NewState->Alternatives = 0;
NewState->Merit = Merit;
NewState->Previous = NewState->Next = NewState->Son = 0;
static Notification *Ntf;
static char *B;
for (Ntf = FirstNotification,
B = (char *) NotifiedSpace; Ntf;
B += Ntf->Size, Ntf = Ntf->Next)
memcpy(B, Ntf->Base, Ntf->Size);
NewState->StoreStack();
setjmp(NewState->Environment);
if (!NewState->Alternatives) {
NewState->Alternatives = N;
NewState->Insert();
TopState->RestoreStack();
}
else {
for (Ntf = FirstNotification,
B = (char*) NotifiedSpace; Ntf;
B += Ntf->Size, Ntf = Ntf->Next)
memcpy(Ntf->Base, B, Ntf->Size);
}
Alternatives = TopState->Alternatives;
Merit = TopState->Merit;
LastChoice = ++TopState->LastChoice;
if (LastChoice == Alternatives)
PopState();
return LastChoice;
}
void Backtrack() {
if (!TopState) {
if (Fiasco)
Fiasco();
Detach();
}
TopState->RestoreStack();
}
I-9
unsigned long NextChoice() {
if (++LastChoice > Alternatives)
Backtrack();
if (LastChoice == Alternatives)
PopState();
else
TopState->LastChoice = LastChoice;
return LastChoice;
}
void Cut() {
if (LastChoice < Alternatives)
PopState();
Backtrack();
}
I-10
void ClearChoices() {
while (TopState)
PopState();
LastChoice = Alternatives = 0;
}
void ClearNotifications() {
while (FirstNotification)
RemoveNotification(FirstNotification->Base);
}
I-11