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 argumentrecord.
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 Wsfirst
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