This document was made by OCR from a scan of the technical report. It has not been edited or proofread and is not meant for human consumption, but only for search engines.

 

B. W. Lampson, J. G. Mitchell and E. H. Satterthwalto Xerox Research Center

3180 Porter Drive

Palo Alto, CA 94304, USA

Abstract

We describe a single primitive mechanism for transferring control from one module to another, and show how this mechanism, together with suitable facilities for record handling and storage allocation, can be used to construct a variety of higher-level transfer disciplines. Procedure and function calls, coroutine linkages, non-local gotos, and signals can all be specified and Implemented in a compatible way. The conventions for storage allocation and • name binding associated with control transfers are also under the programmer's control. Two new control disciplines are defined: a generalization of coroutines, and a facility for handling errors and unusual conditions which arise during program execution. Examples are drawn from the Modular Programming Language; in which all of the facilities described are being Implemented.

1. Introduction

Transfers of control in programs can be divided into two classes. A local transfer stays within the same piece of program text, and does not change the naming environment. A goto which does not involve an exit

from a block has traditionally been the primitive local transfer operation, and other operations have been described by translating them into sequences of (possibly conditional) gotos and assignments. Recently there has' been a lot of effort to find a good set of higher-level local transfer operations, motivated by an awareness that the undisciplined use of the goto results in badly structured programs. The choice of if-then.-else, - for-while and case constructs, sometimes augmented by

loop and exit operations, has met with'wide acceptance. This is

not because of theoretical proofs- that they are sufficient to express any computation, but because many years of experimentation with the possibilities of the goto showed that it is most of fectively used in a few stylized

ways, from which these constructs were abstracted. In fact, the arguments for keeping goto available in programming languages are based on the observation that there are times when its use cannot readily be cast In one of these molds. •

A global transfer, on the other hand, does more than alter the .sequential


flow of control. It usually invokes a new piece of program text, and it always affects the allocation of storage and the binding of names. This paper is about global transfers. In fact, it is an attempt to find a suitable primitive (which we will call transfer) and to describe higher-level global transfers or‑

control disciplines by translating them Into sequences of

transfers, assignments and other data-handling operations.

There are two reasons why this seems worthwhile. First, it is difficult to describe clearly how the control disciplines in existing languages work without resorting to the construction of a formal interpreter [Fisher]. Non-interpretive descriptions either contain large quantities of ambiguous English prose, or they involve operations (such as the Algol 60 copy rule for procedure calls) which may be precise but are certainly not clear. if a language can be used to describe Itself, by defining certain operations in terrns. of sequences of simpler operations In the language, the amount of conceptual baggage required to understand It can be reduced considerably.

Second, It is our opinion that much remains to be learned about the proper choice of global transfer operations. Until recently very few languages other than assemblers gave the programmer any choice of control operations. Simula [Hoare and Dahl] and the new crop of languages for artificial Intelligence have changed this situation to some extent, but it will be a long time before the possibilities for global transfers have been thoroughly explored. If • programmers have the opportunity to create their own control disciplines they will certainly make a.lot of mistakes, but from this experimentation we can hope to learn what works well and what does not.

From this discussion the flavor of the paper should be clear. We will define a transfer primitive and then exploit the local expressive power of the language to describe some control disciplines which we happen to like. This

is not a trivial Job, since a 'good discipline must satisfy a number of constraints:

. programming generality [Dennis] - independently constructed modules can

work together without having to know each other's internal structure. In

particular, each module can choose its names and Its storage allocation

strategies independently of the others;

.   compatibility - modules using different disciplines can still communicate, or the advantages of diversity will be completely overwhelmed by the drawbacks of Babel;

.   robustness - it Is easy to get things set up, and restrictions and caveats are conspiclous by their absence;

.   reconfigurability - connections betweeen modules can easily.be broken and reestablished, so that debugging facilities can be spliced In and the behavior of a module can be changed by attaching "adaptors" to its external connections.

•

This approach should not be misinterpreted. The fact that a construct can be explicated in terms of simpler ones does not mean that the programmer must have this decomposition In mind whenever he uses it. On the contrary, if he uses it as part of his working vocabulary he will normally think of it as an atomic concept. The' explication Is helpful in making the definition precise, and in answering questions about what will happen in unfamiliar situations; It should be thought of In that light.

Questions about the binding of names are, in our view, orthogonal to the study


Un Lne iranSrer 01- %AMU-LH UtilWUU411 l,UIILt AL
•

of transfers, and are not considered in this paper. in particular, rules for binding non-local variables and for linking separately compiled modules are not discussed.

2. The _host language

The facilities- described in this paper are implemented in a general purpose system programming language called the Modular Programming Language (NIPL), which is a component of a system for modular programming. MPL borrows much of its local character from Pascal [Wirth] and EL/1 [Wegbreit]. In particular, it is a typed language in which new types can be built out of old ones using record, array and pointer declarations. There Is

also a way to prevent components of a record from being accessed except by a group of procedures which are declared with it. Such a record is called closed, and the procedures are called its handles. Finally, it is

possible to declare a record type s as a direct extension of another record type r, by adding additional components and handles. Extension is

the transitive closure of direct extension, and the set of all extensions of r is the class of r. Closed records and classes were inspired by the

class mechanism of Simula [Hoare and Dahl]; that language, howeveir, encourages the restriction of access to handles, but does not enforce it... The control transfer operations use closed records and classes to construct and manipulate their data structures.

·      A construct patterned of ter Pascal'S with is used heavily as syntactic sugar by the control disciplines. Any block can be prefixed by one or more clauses of the form:           . •

USING p,

where p is a pointer to a record of type r. Within the block the names of the components of r can then be used without qualification. If c is such a component, then within the block c is short for p.c.

3. Contexts and frames

The entities between which global transfers occur we call contexts.

The definition which follows reflects our views about the properties which such entities ought to have. Although nearly all of the transfer operations of existing programming languages can be described within this framework, we are not making any claims for its universal applicability: Since we make use of its properties in constructing control disciplines, our constructions will not work in systems for which contexts-cannot be defined.

Within this framework we may restate the subject matter of thls.paper:

.  the nature of contexts, and their creation and destruction;

.                minimal primitives which are sufficient to describe any transfer of control between contexts;

.                definition of good higher-level transfer disciplines;

.                description of these disciplines in terms of the primitives.

A context consists of:

.        a pointer to the text of a program, which we shall abstract as an array of objects called Instructions whose Internal structure and


properties are left undefined. We assume that the program is not modified during execution;

. a binding rule for names, which we shall abstract as a function mapping names into pointers;       •

some local storage, including an integer index into the program text called the piogram counter.

3.1 Representation of contexts

A context Is represented by a frame, which is a record whose components contain the information needed to define the context. More precisely, a context base Is a closed record type containing a program text pointer, a binding rule and a program counter; these components are accessible only to the control transfer primitives, which are the handles.               A
context can almost be described as a member of the class of context bases,. Unfortunately, this description cannot quite be taken literally, because we need the transfer primitive to describe how procedures are called, and hence-this primitive cannot be defined as a procedure. The other operations on contexts, however, can properly be defined in this way.

It is interesting to compare this situation with what happens if we try to define as a class some other type which is normally taken as primitive. A 32 bit integer, for example, could be defined as a closed record containing a Boolean array with 32 elements, and we could (rather clumsily) write procedures to implement the standard arithmetic operations, without becoming involved in any circularity. As with any other closed record, these procedures must cooperate in maintaining the consistency of the representation: If add assumes that the integer is represented in 2's complement, then multiply had better not assume sign-magnitude representation. All the questions of consistency are

out in the open, however, since everything having to do with the closed record • is expressed in the declaration of its handles.

For contexts the consistency requirement has a new aspect. The procedure which creates a context, f or instance, must build a data structure which Is consistent not only with the other handles of the class context, but also with the transfer primitive. This Is actually a rather strong requirement,                                                                                                                                      •
because the transfer primitive causes instructions to
be executed from the .program text. When this text was constructed, some assumptions were made (by the compiler) about the environment which would be present during execution of the program. The creation procedure is responsible for setting up the environment so that these assumptions are satisfied. If they are not, chaos will result, since the foundation will be undermined on which the entire

representation of the program is based.                                  If care is taken to satisfy the
assumptions of the transfer primitive, then, we may think of a context as a class, and the remaining discussion will proceed on that basis.

We will call a pointer to a context an lnport. The name is intended to suggest the main purpose of this type, which is to be an operand for the transfer primitive. A pointer to an inport we will call an outport,                         •

with the idea that most of the control disciplines we are interested in need this extra level of indirection so that transfers into a context can be trapped when necessary.

3.2 Creation of contexts


We now proceed to explore In detail how contexts are created. Our discussion concentrates on the logical structure of the creation process, ignoring the details of the implementation, in which much of the work Is done at compile or link time, and many of the operations described are coalesced for ef ficienCy. We say a good deal about the treatment of the types of the various objects Involved in order to make it clear that everything we are doing is consistent with the constraints of a fully typed language.

Since a context is an instance of a class, there must be a single create primitive which takes some arguments and creates a context. The arguments to create are:

.   a record called a program which contains

- an, array of program text,

- the type of the frame- record which the program expects;

.   a frame record (which is not yet a context).

We will consider later where- these records come from.

With these inputs, create's Job is easy. It checks that the frame record actually presented is of the type specified by the program (using the facilities of the type system to find out what the frame's type Is). Then it inserts a pointer to the program text array In the frame, initializes the program counter to zero, and returns the frame record as a context.

A program must be derived eventually from a source file which has been compiled by the MPL compiler. The output of the compiler is an object file which contains the same information as the program record. There Is an operation called load which converts a file name Into a program record,

after checking as best it can that the file Is in fact a legitimate object file (the-type checking machinery cannot be expected to handle this situation perfectly, since It has no control over the-way In which Information Is stored In the file system). The only hard work filed has to do Is to find space for the program text array in the addressable memory of the machine on which the program Is running. How this is done depends on the details of the machine and is not relevant to this paper.

Constructing a' frame is more difficult. Again, we can break this operation down into two parts:

. obtaining storage for the frame;

.  initializing this storage properly and returning it as the frame.

Any record type in MPL has a creation operation associated with it which is defined when the type is declared. This operation accepts a block of storage and perhaps some other parameters,- and produces a record of the proper type. it is the only way to make such a record. The create primitive for . contexts discussed above Is an example of such an operation.

An ordinary frame creator In MPL is a special case of this general mechanism, with two distinctive characteristics. First, it usually has a standard program text, for two reasons:

.  frames tend to have a rather stylized form, so that the differences between them can be efficiently encoded Into a data structure called a, frame descriptor which can then be accepted as a parameter and Interpreted by the standard creator program.

.    there is another circularity problem - someone has to create the frame


creator. A standard creator can Itself be created in a standard way which can be part of the initial system.

The frame descriptor is usually stored In the object file along with the program text. Use of this scheme Is not compulsory, however. All of the facilities for creating records can be used to create frames.

The second unusual thing about a frame creator is that it has to provide the binding function for the context. Recall that this function maps the names used in the program text into pointers to the objects which are bound to those            '
names by this incarnation of the program. This is done by a generalization of . the
display which Is often used to implement the binding rules of Algol. The names used by the program have the form rp.v, where rp is a pointer to the frame of some other context and v is a variable local to'that context. We call the set of frames r, s, t, ... referenced by a context in this way the

neighborhood of the context; it Is defined by a collection rp, sp, tp,                           of
pointers to the frames. The context is automatically prefixed with a clause
of the f orrn

USING rp, sp, tp,                                .     •

so that the program can refer to the variables without qualification, Just as it refers to non-local variables in Algol. One element of the neighborhood Is always the argument record.                         • -

To define the binding function, then, the creator has to define the neighborhood, i.e: set the pointers rp, sp, tp, ... to the proper frames. The type of each frame is of course fixed by the declarations in the source program, but there may several frames of the-same type to choose from.' As far as the typing and control mechanisms of the language are concerned, the creator Is free to choose any of them. One familiar possibility is the Algol rule, which takes the unique textually enclosing occurrence [Wang and Dahl]. In a more complex control environment, however, it may be difficult to define such a unique occurrence, or the programmer may want more flexibility In defining the environment. In any event, the choice of binding rule Is entirely under the programmer's control and Is not relevant to the subject of this paper.

3.3 Storage allocation

There remains the question of storage allocation for a frame.                          This must be
done with some care, since creating a context Is a rather common operation which Is required, for example, by every call of an Algol-like procedure. The standard solution Is to allocate frames from a stack; this works well in a control discipline which ensures that contexts are created and destroyed in last-in first-out fashion. Such a restriction would not be Incompatible with the basic control primitives, but it would-severely constrain the set of compatible higher-level disciplines which would be designed.

In order to avoid this problem, we have made a convention that frames are allocated on a heap; they can then be created and destroyed in any order. A standard non-compacting, coalescing free storage allocator [Knuth) is used, supplemented for speed by a vector of lists of available blocks for all the commonly used sizes. To keep the vector short, frame sizes are quantized by the complier so that they differ by about 10%. Thus the possible sizes might be 10, 12, 14, 16, 18, 20, 22, ..., 200, 220, etc. With this scheme only 40 sizes are required to span the range from 10 to 320, which is a much greater variation than Is likely to be encountered In practice. Furthermore, it Is


un the iransrer or control tretWeen Contexts

always possible to allocate a larger block than the one requested In order to reduce external fragmentation.

4. The transfer primitive

As-we have already seen, in order to handle transfers which change the environment we need at least one language feature orthogonal to that subset of the language•,which is used for programs which run In a single environment.. This section describes a single primitive called transfer to meet this requirement.- We have tried to make this primitive do a minumum amount of work, leaving everything possible to be done by local code surrounding It In the two contexts which are involved in the transfer.

.       -

The basic transfer primitive, then, takes an Inport as its single argument.

Af ter it has been executed, the context which executed it is no longer running, and the context specified by the Inport has started running at the location specified by its program counter. In fact, this operation bears a striking similarity to the primitive used in Multics for switching control from one process to another [Seltzer], where the system scheduler, running within a • user process, picks another process to run and transfers to it. The difference is that in Multics there is no relationship between the processes except for that established by the implementation of the scheduler.

In our case, however, we almost always want to pass some kind of return link and some arguments to the new context. We do this by establishing the convention that the link should be put into a global variable called link, and the argument into another global called ergs, before the

transfer is executed. The context being entered must use the values of these variables, if it cares, before doing another transfer. Since this convention is followed In all our examples, the remainder of the paper uses a three-argument primitive

—

transfer(destination inport, return outport, argument pointer). - as an abbreviation for

deit := destination inport; link := return outport;

ergs := argument pointer; transfer;

In the implementation the global variables deal, link and ergs are of course machine registers.

For obvious reasons we make ergs a pointer to the argument•record.

From the point of view of the type machinery, this will be a "universal" pointer which carries Its type with it. When the receiving context tries to use it, a run-time type check is needed to ensure that it actually has the proper type. In most cases, however, this check can be done at binding time, as we shall see later.                                                                                                  . - '

Note that the transfer primitive says nothing about what is to be done

with the link or the arguments, and it does not create any contexts or allocate any storage. All of this is the responsibility of higher-level conventions or control disciplines, and the existing local features of the language, together with transfer, are sufficient to permit almost all of the transfer operations we know about to be programmed. An actual implementation, of course, may -favor certain disciplines by pre-defining them In a standard prologue and • generating especially good code for them, as ours does for the port, procedure and signal disciplines described below.


5. Conventions for compatible transfers

In defining control disciplines, we would like to have as much compatibility. as possible, so that it Is possible to leave a context using one discipline and enter a second context using a' different one. To make this work, we must be careful about storage allocation and about the rules for handling the arguments and return liriks. We have already discussed a suitably general method for allocating -frames. This section considers the other general problems encountered in designing a fairly broad set of compatible control disclpllr.es.

•

The transfer primitive allows for a single argument, which Is normally a

pointer to the record containing the arguments which the user wanted to pass. The semantics of binding a formal parameter, say x, to an actual parameter, say 14, Is very simple. The sender of the argument record assigns the actual parameter to a suitably named component of the argument record (actualargs.x := 14). When he has finished constructing the record and is ready to transfer, he does

ergs := actuaiargs; transfer(destination).

The receiver does -formalargs := args,

and (automatically) prefixes his block with the clause

USING formalargs,

where formalargs is declared to have the type of the argument record he expects.

The effect of all this is that:

.   the lowrlevel convention for passing arguments is very simple - one pointer is passed;

.   the entire collection of arguments is treated as a unit, so that it can be passed on unchanged by a context which IS simply doing monitoring or tracing and Is not interested' in the Internal structure of the arguments;

.   the receiver can reference the formals with the usual syntax;

.   the language facilities for constructing and decomposing records are automatically available for arguments. These allow, among other things

-    component values to be specified by name, by position or by default;

-    a record to be decomposed by assigning it to an extractor; a

·   syntactic construct which looks exactly like a record constructor except that all the components are treated as left-hand-sides of assignment operators;

-    variable-length records.

In this way a fairly elaborate set of facilities Is made to do double duty without any need to introduce new semantics Into the language.

To preserve generality, we must ensure that the storage occupied by the

. argument record will not be reused until the receiver Is through with it. It is undesirable to put this storage in the sender's frame, as is customary in Algol implementations, because the sender's frame may not live as long as the receiver (e.g. when the sender Is a returning procedure; this case can be handled specially in Algol because of the restrictions on what a function can return). We therefore allocate separate storage for the arguments, and require the receiver to free this storage when he Is done with it.

Copying the entire argument into the receiver's frame would be another


alternative, but is is unattractive for variable length argument records and In situations where a receiver is not interested in the values of the arguments, but Is simply going to pass them on to someone else. Copying does work well f or short argument records, however, especially since the record can.be constructed in the machine's registers, and this strategy Is used for records of -less than 6 words.            .

         6. Coroutines and ports                                                       -

.          •

In this section we take up a pair of control disciplines which treat the two parties to a transfer as equals. In particular, this means that no creation of contexts or allocation of storage is Involved In a transfer, and that the relation between the parties is symmetric - each thinks that it is calling on the other one.

6.1 Coroutines

A coroutine (more or less as in Simula [Hoare and Dahl]; see also

[Mcilroy] and [Conway] ) is a context which, when entered, continues execution where it left off the last time it relinquished control. Local storage survives unchanged from exit to entry (as in a Fortran procedure, Interestingly enough). This is the simplest control discipline, and the easiest to describe. Each context Is pointed to by static inports set up at link time. Hence a transfer passes no return outport. The linkages are normally symmetric, as shown In figure 1.

There are three problems with coroutines of this kind as a general-purpose control discipline.           One is that, because of the fixed linkages, a coroutine
cannot be used to provide a service to more than one customer. A procedure, by contrast, is Ideally suited for this purpose, since it is created as a result of a call and destroyed when its work is done.

A second difficulty is that the control Is entirely anarchic. There is nothing to prevent control from entering a coroutine In an entirely unsymmetric way. For example, in figure 1 context 0 might gain control over inport a from line sl of P, even though its program counter Is at U. If subjected to an appropriate discipline this kind of control transfer might be useful, but no such discipline is present In the simple coroutine scheme.

6.2 initialization of coroutines

The third problem is proper initialization of a collection of coroutines. Recall that a transfer from context P to context Q does not change Q's program counter, but simply causes execution to resume at the point where it stopped, or at the beginning if Q has never run before. Since no buffering of ergs or link is provided by transfer, must save their values before doing another transfer. In general it will do this properlj, only If It is sitting immediately after a transfer to P. in figure 1, for example, If P is started first, it will transfer to Q at sl. but 0 will transfer to R and thus lose P's argument record.

This difficulty can be reduced by initializing more cautiously, as follows: (a) Start each context In turn by transfering to it, let It run up to Its


first transfer, and stop it before it sets up ergs and firth.

(b) Carefully choose one of the contexts and restart it by transferring to it.

Step (a) is unattractive because it requires a kind of control over the internal activities of the contexts which is quite different from what Is needed for normal transfers. Step (b) has more serious problems, which will become apparent on further examination.

Suppose in figure 1 that P is acting as a producer of data and Q as a consumer -who may occasionally return a reply. The fact that P and Q play different' roles Is concealed in the figure by the identical form of the skeletal program text. In figure 2 this difference has been brought out by expanding the argument handling associated with each transfer Into send and receive operations. The sequence of processing is:                                                                                                                             -

P:    setup - send                  transfer - receive - compute - send - transfer -

•••                 -            .

Q:     setup -                         transfer - receive - compute - send - transfer -

The two sequences: are identical except for the phase at initialization: in both cases there is a send - transfer - receive sequence which is the expansion of the simple transfer of ligure 1.

•

The differencein phase is quite Important, however, for step (b) of our cautious initializatioii procedure. If we choose P to restart, it will immediately transfer to 0, which will immediately transfer back, and Ws•first

message will be lodt. If, on the other hand, we choose Q to restart, all will be well. Unfortunately, It is hard to see how to make the proper choice in more complex situations (If indeed it is always possible).

6.3 Processes and messages as a model.

Rather than making further attempts to patch up the simple coroutine discipline, we now turn to a much more powerful scheme: processes executing In parallel and communicating via event channels. This, of course, Is more power than we need or want, but by extracting the essential functions of the parallelism and message buffering we can design a control discipline with understandable properties which preserves the strengths of coroutines while avoiding their - problems

 

The idea of processes executing in parallel we assume to be familiar [Dijkstra]. A message channel is an object on which two basic actions can be performed by a process: send a message and receive a message. A message is an arbitrary record, and_the channel can buffer an arbitrary number of messages. An attempt to receive a message from an empty channel causes the receiving process to wait until a message is sent to that channel. There Is no constraint on the number of processes which can send or receive messages on a given channel.                                      This facility is synthesized from two operating systems
[Lampson, Brinch Hansen]; we have suppressed many details which are Irrelevant to our purpose.

Any transfer operation can now be modeled by some combination of send and receive. We don't have to worry about losing messages, because of the buffering provided by the channels; each process will get around to processing Its messages In due course. Nor is the order in which

" -


processes run of any importance; in fact, it is not even defined, except when processes must wait for messages. We still need-a convention which allows one process to provide service for many customers, however. We get it by analogy with the link parameter of the transfer primitive: an event channel on which to return a reply goes along with each message.

6.4 Ports

The process-channel model has added three essential features to the coroutirie discipline:

.                  parallel execution;

·      buffering of messages;

.                  Indirect access to processes through message channels.

Figure 3 illustrates the structure of a symmetric connection. We now proceed to adapt these features to a sequential, unbuffered environment. The first step is to define a new type for symmetric transfer of control, called a port,-to replace the <channel, outport> pairs in figure 3 [Balzer, Krutar]. Each port is likewise a pair, consisting of an Inport IP and an outport OP. IP points to the context which will get control when a transfer is made through this port, and OP is where the return link will be stored.

We can avoid the need for parallel execution in a straightforward way, by modeling the notion of "a process waiting for a message on a channel" with the new concept of "a context being pending on an Inport". Since a process can only be waiting on one channel, we will insist that a context can only be pending on one inport. Now, if all transfers are to pending Inports, it will always be possible to run the context to which a transfer is directed, and there will be no need for parallel execution. A transfer which does not obey this rule will not be executed, but instead will cause a control fault, with consequences which we will explore shortly. .

Rather than explicitly associating the attribute "pending" with each inport, we can observe that an inport is a capability to start execution of a context, and interpret the pending rule as a requirement that only one non-null inport at a time should exist for each context. The inport components of all the other ports associated with a context Will be null, and a transfer to a null inport will cause a control fault. We thus complicate the semantics of transfer as little as possible.

Note that the pending rule has nothing to do with the transfer primitive, but is a convention which we introduce in order to construct a useful higher-level control discipline, that of ports. Even within this context, it may be proper to break the rule if It can be shown that no untoward consequences will result. Since the-rule is strictly internal to the port discipline, it stands or falls solely on the consistency of that discipline, and it is entirely Independent of the requirements of any other, separate convention for control transfers.                                         •

We do, however, want it to be compatible with a procedure discipline;                        
fortunately, this causes no trouble.

A context gets- to be pending on an Inport In the same way that a process gets to be waiting for a message on a particular channel: by executing a receive operation on the port containing that inport. There Is a definite relationship between the value of the program counter and the pending Inport: the program is at the point where-it expects control to arrive over that port.


As a result, there is no need for message buffering in a successful port transfer, since the receiving context is ready and willing to pick up the message at once.

6.5 Control faults and message buffering

During normal execution a control fault indicates an error, an attempt to transfer control to a context which was not interested in receiving it In that way. During initialization, on the other hand, a control fault may simply be an •Indication that there is another context to start. When a fault occurs, therefore, control Is passed to the owner of the faulting context; the owning context must decide whether another context should be started. The mechanism by which this is done Is described In section 9. Here we confine ourselves to the local consequences of the fault. The argument used above to show that no message buffering is required depended on the absence of control faults. When a fault does occur, what action should be taken to ensure that no messages are lost?

First of all, If no message is being sent (I.e. ergs is null) there is.no need f or buffering. For instance, when two contexts have a strict producer-consumer relationship, transfers from the consumer to the producer involve no message. This explains why no special action was needed during the simple coroutine initialization (discussed In section 6.2 above) when we chose to restart the consumer.

When a control fault occurs during a transfer from P to Gl (see figure 6). and ergs is not null, we actually have to do something. We would like

not to Introduce any new kinds of objects, and not to complicate any existing operations. Since our repertoire of objects and operations is limited, things look unpromising at first sight. Fortunately, however, we do have contexts at our disposal, and within a context we can embed any kind of special processing and storage we want, as long as it interfaces properly to the rest of the world.

In particular, what we can do is to construct a buffer context B with a standard program text, and local storage within which we keep the argument. We want B to emit the argument the next time control is transferred to P through the port a. To get this effect, we put an Inport for B Into a's Inport component, and save the inport for P which normally is there in B's local storage. When B gets control, it will restore P's inport, transmit the saved argument and destroy itself. It does this by executing:

a.lnport := savedinport; transfer(DC, B, (a, savedargument));

where DC Is a system-provided context which destroys B and then does: transfer(a.outport, address(a.innort), savedargument);

The cost of all this machination is quite moderate (which is not actually very important, since control faults take place only at Initialization if there are no errors), and it has the great advantage that normal transfers are not complicated at all by the requirements of control faults. Figure 6 illustrates the successive stages of Initialization for our familiar two-context example.

6.6 Linkage faults

We also want to be able to do dynamic linking, as in Multics [Bensoussan et. - al], so that we must be prepared to deal with a transfer through an