Various Extensions to the occam Compiler
Fred Barnes, Department of Computer Science,
University of Kent.
Last modified 3rd April 2006
As of 1.4.0 (final), the majority of these extensions are enabled by default. Future/ongoing extensions may
require the use of the ``-X7'' flag to kroc.
This document is compatible with versions 1.4.0 (final) and above of KRoC/Linux.
A plain version of this document is available, as is a clean version (keeping syntax highlighting).
Proposed occam-pi extensions (which include these) can be found here.
added 20/03/2005
The support for channels of any channel type has now been extended to support variables and parameters of the "MOBILE.CHAN" type. Variables
of this type (that can be specialised in the usual way with "SHARED" and/or a direction-specifier) may be communicated on matching channels, or assigned to each other.
Furthermore, a regular mobile-channel-end may be assigned to a "MOBILE.CHAN" and visa-versa (with an additional run-time check in the latter case to ensure the correct type
is present). For example:
CHAN TYPE FOO
MOBILE RECORD
CHAN INT c?:
:
MOBILE.CHAN x, y:
FOO! f.c:
SEQ
x := f.c
y := x
f.c := y
added 13/03/2005
Mobile barriers are mobile equivalent of static barriers. These barriers must be explicitly allocated through the use of a special
mobile assignment (that follows the style of other dynamic mobile allocation):
MOBILE BARRIER b:
SEQ
b := MOBILE BARRIER
Unlike static barriers, mobile barriers may be freely communicated and assigned except when extended by a "PAR BARRIER". Extended barriers
may not be over-written, but may still be used as the source in assignment or communication. For example:
PROC recv (CHAN MOBILE BARRIER in?)
MOBILE BARRIER x:
SEQ
in ? x
SYNC x
:
PROC send (CHAN MOBILE BARRIER out!)
MOBILE BARRIER v:
SEQ
v := MOBILE BARRIER
out ! v
SYNC v
:
CHAN MOBILE BARRIER c:
PAR
send (c!)
recv (c?)
Following the semantics of communication, mobile barriers may be passed to "FORK"ed processes, that can overwrite
their mobile barrier parameters even when extended. Mobile barriers may also be "RESIGN"ed in the normal way. For example:
PROC foo (MOBILE BARRIER x)
SEQ
SYNC x
x := MOBILE BARRIER
SYNC x
:
MOBILE BARRIER b:
SEQ
b := MOBILE BARRIER
PAR BARRIER b
FORK foo (b)
SYNC b
RESIGN b
added 26/02/2005
This extension provides a "BARRIER" type, used to synchronise multiple parallel processes (channels synchronise two processes only). Barriers may be
declared and renamed (passed as parameters and abbreviated) like ordinary variables, but may not be communicated or assigned. Normal parallel-usage rules apply
to barriers, except when a "PAR" construct extends a barrier (specified using "PAR BARRIER"). For example:
PROC foo (BARRIER x)
SEQ
SYNC x
SYNC x
:
BARRIER b:
PAR BARRIER b
foo (b)
foo (b)
Processes may resign themselves from a barrier, but this introduces non-determinism. For example:
BARRIER b:
PAR BARRIER b
SYNC b
SEQ
RESIGN b
SYNC b
The default behaviour of the compiler is to resign parallel processes from the barrier as soon as they terminate -- without waiting for the whole "PAR"
to terminate before resigning the processes.
added 19/01/2005
This addition allows a channel to carry a special "any channel-type" protocol. This is essentially the same as variant (tagged) communication, except that the tag
is implied by the channel-type. For example:
CHAN TYPE LINK
MOBILE RECORD
CHAN INT c?:
:
PROC recv (CHAN MOBILE.CHAN in?)
LINK! l.cli:
SHARED LINK! l.scli:
in ? CASE
l.cli
l.scli
:
PROC send (CHAN MOBILE.CHAN out!)
LINK! l.cli:
LINK? l.svr:
SEQ
l.cli, l.svr := MOBILE LINK
out ! l.cli
:
These could be connected with just:
CHAN MOBILE.CHAN c:
PAR
send (c!)
recv (c?)
Or something more elaborate.. As far as parameters and abbreviations are concerned (renaming), channels of the any channel-type are only type-compatible with
themselves. More specialised declarations can also be made, e.g. to only carry shared channel-ends or client/server channel-ends. For example:
CHAN SHARED MOBILE.CHAN? c:
CHAN MOBILE.CHAN! d:
The "MOBILE.CHAN" type may also be used to declare any-channel-type variables and parameters. These may be directly inputted or outputted to a correspondingly
typed channel, including use in sequential/variant protocols. For example:
PROTOCOL ILINK
CASE
get.status
route; INT; MOBILE.CHAN
:
PROC linkify (CHAN MOBILE.CHAN in?, CHAN ILINK out!)
WHILE TRUE
MOBILE.CHAN x:
SEQ
in ? x
out ! route; 0; x
:
Ordinary channel-types may also be output as a "MOBILE.CHAN" protocol component, but not input (since there is no clear way to define this in the language syntax).
"MOBILE.CHAN" variables are covered in detail above.
added 21/12/2004
This addition allows the programmer to specify specific values for the tags in tagged (variant) protocols. This allows external C code (using the CIF framework) to
communicate on variant protocol channels with occam processes, without relying on (occam-pi) compiler-generated constants. Tag values are specified in a natural way,
for example:
PROTOCOL EXTERNAL.LINK
CASE
mouse.x.y = 0; INT; INT
mouse.down = 2; INT
mouse.up = 3; INT
:
The compiler will check that specified tag-values do not conflict when dealing with nested protocols and/or inherited protocols.
Additionally the compiler will disallow any tagged protocol with a mixture of value-specified and non-value-specified tags.
added 05/05/2004
This extension adds support for MOBILE processes in `occam-pi' (the new name for the evolving occam-originating language). Mobile processes
have an associated process-type that characterises the interface to the process. Mobile processes encapsulate code and data, like any other occam process,
but may be moved around a process network. The main restriction is that a mobile process may not be moved whilst it is active. Activations of a
mobile process are implemented using an `instance-style' syntax. A mobile process ceases to be active when it terminates or `SUSPEND's. Once it has terminated,
a mobile process may not be reactivated.
The following code defines a fairly simple mobile process (and associated process-type):
PROC TYPE PT IS (CHAN INT in?, out!):
MOBILE PROC integrate (CHAN INT in?, out!) IMPLEMENTS PT
INT total:
SEQ
total := 0
WHILE TRUE
INT v:
SEQ
in ? v
total := total + v
out ! total
SUSPEND
:
This implements the familiar `integrate' component. When activated, the process will complete one cycle of input and output then suspend. Allocation of a mobile process
is done in a similar way as it is for other dynamic mobile types -- with a slightly special form of assignment. For example:
MOBILE PT x:
INT v:
SEQ
x := MOBILE integrate
CHAN INT to.int, from.int:
PAR
x (to.int?, from.int!)
to.int ! 42
from.int ? v
After the activation has finished (because `integrate' SUSPENDed), the mobile process in `x' may be assigned or communicated. When it is next
activated, the process may have a completely different environment.
Due to the somewhat special nature of a mobile process activation, there are several restrictions on what parameters may appear in a `PROC TYPE' definition -- only
synchronisation `objects' may be used as parameters. This currently includes channels, fixed-size arrays of channels and mobile channel-bundle ends.
If a mobile process goes parallel internally, all parallel processes must SUSPEND in order to suspend the mobile process as a whole.
This example program demonstrates this using a parallel version of the `integrate' process. Rather than suspending
once per cycle, the process interprets `MOSTNEG INT' on its input channel as `suspend'. This is one way of handling the suspend -- it could just as easily be
supported using dedicated channels (that may be more desirable for clarity).
added 04/01/2004
Support for protocol inheritance has now been added to the occam compiler. This allows tagged (variant) protocols
to inherit tags from others, creating a type-compatibility between them.
For example:
PROTOCOL MOUSE.EVENTS
CASE
mouse.x.y; INT; INT
mouse.down; INT
mouse.up; INT
:
PROTOCOL KEY.EVENTS
CASE
key.down; INT; INT
key.up; INT; INT
:
PROTOCOL GFX.EVENTS EXTENDS MOUSE.EVENTS, KEY.EVENTS
CASE
gen.error; INT
:
This can be used in the expected way. Given the set of PROCs:
PROC graphics.server (CHAN GFX.EVENTS in?)
PROC mouse.driver (CHAN MOUSE.EVENTS out!)
PROC keyboard.driver (CHAN KEY.EVENTS out!)
We can wire them up this way:
CHAN GFX.EVENTS c:
PAR
mouse.driver (c!)
graphics.server (c?)
or this way:
CHAN KEY.EVENTS c:
PAR
keyboard.driver (c!)
graphics.server (c?)
These show the two allowed ways of using protocol inheritance. There are two others which are not legal, however, and are banned by the compiler.
In the first example, there would normally be a type incompatability in the instance of `mouse.driver', since a channel of a different type
is passed as a parameter. However, since `GFX.EVENTS' (actual type) includes all the tags of `MOUSE.EVENTS' (formal type), anything
that `mouse.driver' outputs is potentially handled (by a process that inputs `GFX.EVENTS').
In the second example, the incompatability is the instance of `graphics.server'. A similar logic as before applies, however. The
`graphics.server' process accepts (potentially) any of the `GFX.EVENTS', of which `KEY.EVENTS' are a subset.
The two invalid cases are substantially less natural. Assuming there was:
PROC mouse.handler (CHAN MOUSE.EVENTS in?)
PROC graphics.plex ([]CHAN GFX.EVENTS in?, CHAN GFX.EVENTS out!)
Then:
CHAN MOUSE.EVENTS c:
PAR
graphics.plex (..., c!)
mouse.handler (c?)
and
CHAN GFX.EVENTS c:
PAR
graphics.plex (..., c!)
mouse.handler (c?)
are illegal. In the first, for example, an attempt is made to connect a channel of `MOUSE.EVENTS' to a process that outputs
any `GFX.EVENTS', of which `MOUSE.EVENTS' are possibly only a subset -- i.e. `graphics.plex' could output
something that couldn't be handled by `mouse.handler'. And that makes good sense. The second example is similar,
but with the error being at the other end of the channel (an attempt to connect an input channel carrying potentially any `GFX.EVENTS'
to a process that can only handle `MOUSE.EVENTS').
There are several restrictions associated with protocol inheritance, that are largely technical. The compiler will abort when it finds
cases that it can't handle.
added 30/12/2003
Support for forward declarations of channel-types has now been added to the occam compiler. This allows
the use of a channel-type name before its actual definition. Unlike forward declarations for
procedure (names), forward declarations of channel-types do not add any recursion-related problems (an
equivalent would be outputting an output, and so on, which isn't legal occam).
The practical up-shot is that channel-types may carry ends of each other recursively; or that
a channel-type may carry itself (essentially a recursive channel-type).
For example:
CHAN TYPE BAR:
PROTOCOL FOO.IN
CASE
a.bar.client; BAR!
:
CHAN TYPE FOO
MOBILE RECORD
CHAN FOO.IN in?:
:
PROTOCOL BAR.IN
CASE
a.foo.client; FOO!
:
CHAN TYPE BAR
MOBILE RECORD
CHAN BAR.IN in?:
:
Or more simply:
CHAN TYPE BLIP:
CHAN TYPE BLIP
MOBILE RECORD
CHAN BLIP! blip.in?:
:
which is equivalent to a `RECURSIVE' declaration:
RECURSIVE CHAN TYPE BLIP
MOBILE RECORD
CHAN BLIP! blip.in?:
:
added 09/08/2003
Support for some basic nested MOBILE types has now been added to the occam compiler. These represent only a fraction of the
possible nested mobile type-system, but are a start. The two types currently supported are limited to single-level nesting and were
implemented first because of a need for them.
They are:
- dynamic mobile arrays of dynamic mobile arrays
- dynamic mobile arrays of mobile channel-ends
Examples of these:
MOBILE []MOBILE []BYTE messages:
MOBILE []SHARED FOO! s.foo.clients:
Note that the first example and the type `MOBILE [][]BYTE' are very different -- the latter is a single-level array of two dimensions
(a 2D matrix of BYTEs), whilst the former is a two-level array, each of one dimension (an array of arrays of BYTEs).
Any channel-type `end-type' is valid for arrays of channel-ends, shared or unshared, client or server.
Initialisation of such data is as expected -- the outer array must be allocated first. For example:
MOBILE []MOBILE []BYTE msgs:
INT i:
SEQ
i := 10
msgs := MOBILE [i]MOBILE []BYTE
msgs[0] := "hello, world!*n"
msgs[1] := msgs[0]
out.string (msgs[1], 0, screen!)
added 20/03/2003
For the majority of programming languages, once defined, a variable stays that way -- there is no clear
way to undefine variables in most languages (with some exceptions, such as assigning from a function whose
result is undefined). In occam, ordinary variables are considered undefined until assignment or input to.
Once defined, they stay that way. Mobile variables, however, can transition between undefined and defined,
based on the program logic (there is also other definedness states -- partially defined and possibly
defined amongst them).
At some points in a program, it may be unknown (in the program logic) whether a particular variable is
defined or undefined. For example, consider a mobile channel-end -- it may or may not be connected to
a server. Or a dynamic mobile array -- it may either contain zero elements (undefined) or some elements
(defined), separate from the definedness states of those elements.
The `DEFINED' operator provides a way of testing this at run-time, with subtle effects on the
undefinedness checker. For example, the following code fragment is valid:
CHAN TYPE FOO
MOBILE RECORD
CHAN INT c?:
:
FOO! f.c:
SEQ
IF
DEFINED f.c
f.c[c] ! 42
TRUE
FOO? f.s:
SEQ
f.c, f.s := MOBILE FOO
FORK server (f.s)
f.c[c] ! 42
Attempts to use the DEFINED operator with non-dynamic mobiles (i.e. static mobiles and ordinary variables)
results in a compiler error. This may be modified in the future to work with other types.
added 16/03/2003
A limited support for pre-processing has now been added to occam. Most usefully, it adds a mechanism for
conditional compilation, using pre-processor constants. Given the issues relating to the combination of
conditional compilation and occam's indentation, the pre-processor is implemented as part of the compiler,
with a special directive for limited control of indentation.
For example:
#DEFINE USE.INT.OUT
PROC write.int (VAL INT x, CHAN BYTE out!)
#IF DEFINED (USE.INT.OUT)
out.int (x, 0, out!)
#ELSE
out.number (x, 0, out!)
#ENDIF
:
This would cause the use of `out.int' inside `write.int', instead of `out.number'. Removing
the `#DEFINE USE.OUT.INT' (or commenting it out) would reverse this.
The above example demonstrates the obvious problem with indentation -- it would be nice if the contents of conditional
compilation blocks were indented, but it would be somewhat extreme to always enforce this (temporary `#IF FALSE'ing of code, for example).
Therefore, a special `#RELAX' directive is provided, that `relaxes' the indentation inside a conditional compilation block.
Re-writing the above, for example:
#DEFINE USE.INT.OUT
PROC write.int (VAL INT x, CHAN BYTE out!)
#IF DEFINED (USE.INT.OUT)
#RELAX
out.int (x, 0, out!)
#ELSE
#RELAX
out.number (x, 0, out!)
#ENDIF
:
In addition to simply defining pre-processor constants, they may be defined (and re-defined) with values. The values used may either be integers or strings.
Anything else (besides nothing) is invalid. When referring to pre-processor constant values in code, the name must be prefixed with `##'.
Such substitutions are always literal.
The compiler, before parsing of a file starts, defines various pre-processor constants. Some of these depend on compiler build-time or run-time options.
These are:
| Name | Type | Description |
| PROCESS.PRIORITY | integer | if defined, the number of process priority levels supported |
| OCCAM2.5 | none | if defined, indicates that support for user-defined types and other occam 2.1 features is available |
| USER.DEFINED.OPERATORS | none | defined if user-defined operators are supported |
| INITIAL.DECL | none | defined if INITIAL declarations are supported |
| MOBILES | none | defined if MOBILEs (all types) are supported |
| BLOCKING.SYSCALLS | none | defined if blocking system-calls are supported |
| VERSION | string | compiler version string |
| NEED.QUAD.ALIGNMENT | none | defined if the target architecture requires 64-bit alignment of data |
| TARGET.CANONICAL | string | canonical compiler target name, i.e. the host type that `occ21' was compiled for |
| TARGET.CPU | string | target CPU -- the CPU type that the compiler runs on |
| TARGET.OS | string | target OS -- the OS that the compiler runs on |
| TARGET.VENDOR | string | target vendor -- the hardware in use (e.g. `pc') |
There is nothing to prevent a file re-defining or un-defining these. In general, it is not a good idea, however. Two additional built-ins are also maintained
by the compiler, `FILE' and `LINE'. These refer to the current file-name and line-number respectively. For example:
PROC message (VAL []BYTE file, VAL INT line, CHAN BYTE scr!)
SEQ
out.string ("Hello from ", 0, scr!)
out.string (file, 0, scr!)
out.string (", line ", 0, scr!)
out.int (line, 0, scr!)
scr ! '*n'
:
PROC main (CHAN BYTE kyb?, scr!, err!)
SEQ
message (##FILE, ##LINE, scr!)
:
The mechanism for conditional compilation is formed using `#IF', `#ELIF', `#ELSE' and `#ENDIF'. The expressions used in
`#IF' and `#ELIF' are similar to occam expressions, but should not be mistaken for them. Such expressions must be boolean and fully bracketed.
The values of pre-processor constants may be referred to simply by name (omitting the `##'), and compared with constants and each other using a limited
range of boolean operators.
The three basic tests/expressions are `TRUE', `FALSE' and `DEFINED (name)', that tests whether or not a particular name
is a pre-processor define. For comparing strings are the operators `<>' and `='. For comparing integers, `<>', `=',
`<=', `<', `>' and `>='. For combining/modifying the results of sub-expressions are `NOT', `AND' and `OR'.
Evaluation of boolean tests is done in strict lazy order, so that one can write, for example:
#IF (NOT DEFINED (PROCESS.PRIORITY)) OR (PROCESS.PRIORITY < 32)
#RELAX
#ERROR not enough or no priority!
#ENDIF
The two special pre-processor directives `#ERROR' and `#WARNING' are used to emit errors and warnings respectively. An error will abort compilation.
Substitutions of pre-processor values in the message are done using the `##' prefix.
A more interesting example is where conditional compilation alters the logic of an occam program (alteration beyond clarity is not recommeded, however). For example:
WHILE TRUE
#IF DEFINED (USE.PRIALT)
PRI ALT
#ELSE
ALT
#ENDIF
added 13/02/2003
Traditionally in occam, producing an array of size zero has been difficult -- using a zero-length slice of another array. Attempts to write `[]' would
be banned by the compiler. You could write `""' for an empty array of BYTEs, however.
This has now been remedied. The literal empty array `[]' may be used where appropriate (and legal). Additionally, `[]' is allowed as a parameter to
non-VAL formals, and in abbreviations (both renaming). For example:
SEQ
out.string ([], 0, scr!)
[]BYTE x IS []:
out.string (x, 0, scr!)
The abbreviation is not something that would be written normally, but can happen when PROC calls are inlined. Note that although `[]' is
valid for non-val renamings, `[[]]' is not -- the outermost array contains a single element.
added 03/09/2002
Recursive channel-types add a useful feature to the existing mobile channel-types -- the ability for a channel within a mobile channel-type
to transport ends of its own type. This is particularly useful in client-server networks that involve one-to-one connections between clients and servers,
established over shared channels -- it provides a means for the client to say ``i'm done'' to the server. For example:
RECURSIVE CHAN TYPE BUF.MGR
MOBILE RECORD
CHAN INT req?:
CHAN MOBILE []BYTE resp!, ret?:
CHAN BUF.MGR! done?:
:
PROC server (SHARED CHAN BUF.MGR? clients?)
BUF.MGR? link:
WHILE TRUE
SEQ
CLAIM clients?
clients ? link
INITIAL BOOL ok IS TRUE:
WHILE ok
ALT
INT size:
link[req] ? size
BUF.MGR! cli:
link[done] ? cli
ok := FALSE
:
PROC client (SHARED CHAN BUF.MGR? to.servers!)
SEQ
BUF.MGR? svr:
BUF.MGR! cli:
SEQ
svr, cli := MOBILE BUF.MGR
CLAIM to.servers!
to.servers ! svr
cli[done] ! cli
:
The interesting bit is the last action performed by the client, `cli[done] ! cli', that returns the channel-end to the server, to which it is connected. One way
to think of this is to imagine the two processes connected by a hose-pipe, where the returning action turns the hose-pipe in on itself at the client end, until it `pops out'
at the server end.
For completeness, a network using the above `client' and `server' processes is easily constructed (using anonymous channel-types and
forked processes):
PROC network (VAL INT n.cli, n.svr)
SHARED CHAN BUF.MGR c:
FORKING
SEQ
SEQ i = 0 FOR n.cli
FORK client (c!)
SEQ i = 0 FOR n.svr
FORK server (c?)
:
added 03/09/2002
Anonymous channel-types provide a convenience. Quite often, we want a mobile channel-end of only one channel. The usual way would be to declare a
mobile channel-type with a single channel, and use that. For example:
CHAN TYPE THING
MOBILE RECORD
CHAN INT c?:
:
THING? svr:
SHARED THING! cli:
SEQ
svr, cli := MOBILE THING
PAR
SEQ i = 0 FOR 2
INT x:
svr[c] ? x
CLAIM cli
cli[c] ! 42
CLAIM cli
cli[c] ! MOSTNEG INT
This is somewhat cumbersome, given the simple nature of what we're trying to do -- share a single channel. An equivalent version, using anonymous channel
types, is:
SHARED! CHAN INT c:
PAR
SEQ i = 0 FOR 2
INT x:
c ? x
CLAIM c!
c ! 42
CLAIM c!
c ! MOSTNEG INT
This is much easier to follow, and it is still largely clear what is going on.
Anonymous channel-types are created by use of the `SHARED' keyword in an ordinary channel declaration. By default, both ends of the anonymous
channel type will be shared, unless restricted by an additional `?' or `!' (as in the above fragment). Two restrictions apply to
anonymous channel-types. Firstly, whenever referred to by name, a channel-direction specifier must be used to indicate the client or server end (e.g. `c!'
for the client-end). Secondly, anonymous channel types may not be assigned or communicated, but they may be renamed. The one exception is that the
compiler allows an anonymous channel-type to be used as a parameter for a FORKed process. Additionally, the KRoC run-time system supports anonymous
channel-types for the standard top-level parameters (in addition to different combinations of top-level parameters). For example:
PROC say.hello (SHARED CHAN BYTE out!)
VAL []BYTE message IS "hello, forked and shared world!*n":
CLAIM out!
SEQ i = 0 FOR SIZE message
out ! message[i]
:
PROC example (CHAN BYTE kyb?, SHARED CHAN BYTE scr!)
FORKING
SEQ
FORK say.hello (scr!)
FORK say.hello (scr!)
:
As can be inferred from the above example, when an anonymous channel-type is used as a formal parameter, any restriction on the SHARED can
be omitted.
Inside the body of a CLAIM an anonymous channel-type behaves like an ordinary occam channel. This includes renaming, which is not allowed
for ordinary mobile channel-types. Typical usage, for example:
#USE "course.lib"
PROC simple (SHARED CHAN BYTE scr!)
PAR
CLAIM scr!
out.string ("hello world 1!*n", 0, scr!)
CLAIM scr!
out.string ("hello world 2!*n", 0, scr!)
:
added 01/09/2002
The `FORK' provides a means for dynamically creating a free-running parallel process. A type of `join' is available by use of a `FORKING' block, that
acts as a barrier on which forked processes synchronise. If a FORK is not enclosed in any FORKING block, it runs freely.
The type of FORK supported is that of a new PROC instance. When instanced normally, PROC parameters use a renaming semantics.
When FORKed, the parameters use a communication semantics. This means that non-communicable parameter types are not allowed in a FORK.
This restriction is typically channel and reference parameters. However, if the need really arises, such things can be declared `#PRAGMA SHARED' and passed
anyway. Any mobile type is, of course, allowed.
The use of `FORK' is likely to be application-specific (particularly useful for building server-farms -- see
Prioritised dynamic communicating processes: Part 2, downloadable from here). A
simple example of the FORK is:
PROC big.sum (VAL []INT data)
:
PROC application (CHAN BYTE kyb?, scr!, err!)
FORKING
MOBILE []INT data:
SEQ
FORK big.sum (data)
:
After the FORK, `data' will remain defined. Since the formal parameter type is `VAL []BYTE', the data will be copied locally into the FORKed
process, leaving the source (a mobile in this case) alone.
Connecting a FORK process to the rest of the process network is best done using mobile channel types or
anonymous channel types. For example:
CHAN TYPE FOO
MOBILE RECORD
CHAN INT in?:
CHAN INT out!:
:
PROC server (FOO? link)
WHILE TRUE
:
PROC client (SHARED FOO! link)
CLAIM link
:
PROC network ()
FOO? f.s:
SHARED FOO! f.c:
SEQ
f.s, f.c := MOBILE FOO
FORK server (f.s)
SEQ i = 0 FOR 10
FORK client (f.c)
:
added 30/06/2002
Structured channel-types provide a mechanism for grouping related channels together inside a `RECORD' type. Ordinarily, these types are declared to be mobile.
This provides a mechanism for moving bundles of channel ends around inside a process network.
Starting with an example type declaration:
CHAN TYPE FOO
MOBILE RECORD
CHAN INT request?:
CHAN INT response!:
:
A channel in occam has two ends -- one for reading and one for writing. Channel types (bundles) have two conceptual ends, termed `server' and `client'.
Channel direction-specifiers must be used inside the channel-type declaration, so that the compiler can enforce correct
usage on a particular end (client or server).
Declaration and initialisation of a channel-bundle (mobile channel-type) is relatively simple. For example, using the above declaration of `FOO':
FOO! foo.c:
FOO? foo.s:
SEQ
foo.c, foo.s := MOBILE FOO
The allocating assignment is similar to that used with dynamic mobile arrays (see the mobiles section). A bundle of channels is dynamically
created and the two ends assigned to `foo.c' and `foo.s'. The order in which the end-variables are given is unimportant; what matters is that one is
a client-end and the other a server-end of the same mobile channel type.
The channel-ends within a channel-bundle are accessed using the familiar record subscription syntax. For (a slightly expanded) example:
CHAN TYPE BUF.MGR
MOBILE RECORD
CHAN INT request?:
CHAN MOBILE []BYTE response!:
CHAN MOBILE []BYTE return?:
:
PROC server (BUF.MGR? link)
WHILE TRUE
INT n:
MOBILE []BYTE b:
SEQ
link[request] ? n
b := MOBILE [n]BYTE
SEQ i = 0 FOR SIZE b
b[i] := 0 (BYTE)
link[response] ! b
link[return] ? b
:
PROC client (BUF.MGR! link)
MOBILE []BYTE buf:
SEQ
link[request] ! 1024
link[response] ? buf
link[return] ! buf
:
PROC network ()
BUF.MGR! cli:
BUF.MGR? svr:
SEQ
cli, svr := MOBILE BUF.MGR
PAR
server (svr)
client (cli)
:
Unfortunately, an array constructor cannot be used (at present) in the `server' process to create the array, since
the size of an array-constructor must be a compile-time constant.
The above `network' allocates a `BUF.MGR' channel bundle then runs client and server processes in parallel, connected by that bundle.
Because the channel-ends are mobile, however, they can be communicated (and assigned). Thus, we might `wrap' the earlier `client' and
`server' processes such that the ends (on which to communicate with each other) are inputted first. For example:
PROC w.server (CHAN BUF.MGR? link.in?)
BUF.MGR? link:
SEQ
link.in ? link
server (link)
:
PROC w.client (CHAN BUF.MGR! link.in?)
BUF.MGR! link:
SEQ
link.in ? link
client (link)
:
The modified `network' process would be:
PROC w.network ()
CHAN BUF.MGR! c.cli:
CHAN BUF.MGR? c.svr:
PAR
w.server (c.svr?)
w.client (c.cli?)
BUF.MGR! cli:
BUF.MGR? svr:
SEQ
cli, svr := MOBILE BUF.MGR
c.cli ! cli
c.svr ! svr
:
Although such channel-bundle ends are mobile, applying the CLONE operator (to produce a mobile copy) makes little sense -- how would access between copies be controlled ?
or, would new server processes be automatically generated..?.
The paradigm of a shared server is one that has long existed in occam -- usually controlled by ALTing
over multiple channels. To capture this more effectively, channel-bundle ends may be declared `SHARED'. This allows for four different arrangements of client/server
connections: single client, single server; multiple clients, single server; single client, multiple servers; and multiple clients with multiple servers.
To control access to shared channel-ends, CLAIM blocks must be used. Modifying the `client' process for example:
PROC client (SHARED BUF.MGR! link)
CLAIM link
MOBILE []BYTE buf:
SEQ
link[request] ! 1024
link[response] ? buf
link[return] ! buf
:
Parallel processes compete for access to a CLAIM block by means of a semaphore, on which they queue in FIFO order. The rules pertaining to nested CLAIMs
are that no nested claims are allowed inside a client-end claim, and that server claims may be nested (possibly with a client claim at the innermost point). This prevents
partially-aquired resource deadlock on clients, but not on servers. Neither does it prevent cyclic deadlock. Avoiding deadlock through the use of CLAIM is a
program design issue. Possible (or partial) compiler-based solutions to this are being thought about -- strict ordering and multi-claims (`CLAIM x, y') may be
one solution.
Inside a CLAIM block, the channel-end being CLAIMed behaves largely as its non-shared version. However, it may not be input or assigned to, or renamed.
This ensures that whatever is CLAIMed doesn't move (and isn't given the opportunity to through renaming -- less obvious in separate compilation).
For shared ends, the CLONE operator is meaningful. In fact, when dealing with shared ends, the compiler will always CLONE for assignment or communication,
regardless of whether the `CLONE' keyword is present. If the need ever arises to forcefully release a channel-end, it can be done by declaring it undefined to the
compiler (that will insert code if it thinks otherwise). For example:
SHARED FOO! f.c:
SHARED FOO? f.s, f.other:
SEQ
f.c, f.s := MOBILE FOO
f.other := f.s
#PRAMGA UNDEFINED f.s
The above examples show only a small amount of what can be achieved using mobile channel-types. For a fuller analysis, see the papers
``Prioritised dynamic communicating processes: Part 1'' and
``Prioritised dynamic communicating processes: Part 2''. The texts of these papers are
available on my publications page, and also from the WoTUG website.
added 04/02/2002
Support for 32 levels of process priority has been added to the occam system. These range from 0 (highest
priority) to 31 (lowest priority). Most of the hard work related to priority handling is done in the
run-time kernel. In occam, priority is handled using the following compiler pre-defines:
INT FUNCTION GETPRI ()
PROC SETPRI (VAL INT p)
PROC INCPRI ()
PROC DECPRI ()
These simply end up as calls to two new transputer instructions, `GETPRI' and `SETPRI'.
By default, the top-level occam process starts at the highest priority (0).
added 14/01/2002
Placed channels are what constitutes the compiler support for user-defined channels. Placed arrays
allow occam programs to use externally allocated memory. While fairly separate in function, placed arrays
and channels share a large amount of compiler code, thus their description together here.
Placed channels can be defined in one of two ways:
INT addr:
SEQ
CHAN INT c!:
PLACE c! AT addr:
or:
INT addr:
SEQ
PLACED CHAN INT c! addr:
The second example demonstrates the optional OF and optional AT. These
channels are flagged as being placed by the compiler. When communication is performed on these channels,
a second set of I/O instructions is used: EXTIN, EXTOUT, EXTOUTWORD, EXTOUTBYTE,
EXTENBC and EXTNDISC. The operation of these is similar to the same non-EXT instructions.
When the channel is PLACEd, the EXTVRFY instruction is generated to check the channel before using
it for communication.
Currently not supported for placed channels are MOBILE communication and extended rendezvous. There
is documentation on-the-way for using placed channels and some example code is provided in the KRoC/Linux distribution.
Placed arrays allow occam programs to access external memory, much like the transputer did. Rather than having a
static address, the address used in the PLACEment can be run-time computed. Both the two-line placement and
the single-line PLACED syntaxes are supported, for example:
INT addr:
SEQ
C.malloc (#8000, addr)
PLACED [#8000]BYTE data addr:
The hash character is just the occam way of specifying hexadecimal numbers. For i386 systems, a special compiler-directive
exists (`#PRAGMA IOSPACE') which arranges for i/o space to be used, rather than memory space. This would
typically be used, for example, to access the VGA registers, parallel port registers, etc., for example:
INT io.base, io.size:
SEQ
io.base := #378
io.size := 8
PLACED [8]BYTE parport0 io.base:
#PRAGMA IOSPACE parport0
SEQ
BYTE b:
b := parport0[0]
Note: because placed channels and arrays allow access to memory outside the normal workspace/vectorspace,
there is a high degree of risk associated with using them. That is, if a bad address is supplied to the PLACE,
undefined behavior may result.
added 10/01/2002
The ALT disabling sequence has been modified to perform a reverse disable in the case of ``PRI ALT''s,
and a reversed ``PRI ALT'' in the case of plain ``ALT''s.
Three new instructions have been added to the instruction set which are now used in place of the three
existing alternative-disable instructions (``DISC'', ``DIST'' and ``DISS'').
These new instructions behave differently in that the guard will always be selected if it is ready,
rather than being selected if it is ready and nothing had been ready previously, which was the default
behavior with ``DISC'', etc. These new instructions are called ``NDISC'', ``NDIST''
and ``NDISS''. The input and output registers used are the same as the existing ones.
For example, the code:
ALT
a ? x
P
b ? y
Q
c ? z
R
will enable ``a'', ``b'' and ``c'' in that order, do the alternative wait if none were ready, then
disable in the same sequence using the ``NDISC'' instruction. If ``c'' is ready, it will be
be selected, since it is the last guard examined in the disable sequence, regardless of whether ``a''
and/or ``b'' were ready. The ``PRI ALT'' is handled in a similar way, except that the guards
are examined in reverse. For example:
PRI ALT
a ? x
P
b ? y
Q
c ? z
R
will have the guards disabled in the order ``c'', ``b'' then ``a''. Thus, if ``a'' is ready, it will
be selected regardless of whether ``b'' and/or ``c'' were ready.
For the most part, correct programs will run as they did. Programs which assume a ``PRI ALT''
for a regular ``ALT'' may behave incorrectly. There are some cases where this might not be
immediately obvious though, for example:
TIMER tim:
INT t:
BOOL got.first:
SEQ
got.first := FALSE
tim ? t
ALT
tim ? AFTER (t PLUS 10000)
got.first := TRUE
tim ? AFTER (t PLUS 20000)
IF
got.first
SKIP
will often find itself going ``STOP'', when ``got.first'' is false in the IF (last 3 lines).
This is because a fine-grained timer is not guaranteed with the current run-time system (KRoC/Linux), if you ask to sleep
for 10ms, it might be after 20ms when you wake up. In the old ALT disabling sequence, the first guard would
always be selected, since the ``DIST'' on the second would not select that process. ``NDIST'' will
select the second process, if that time has expired.
added 19/12/2001
The extended rendezvous allows an inputting process to specify a process which will be executed after
the communication has been performed, but before the outputting process resumes. The syntax for these
uses the double question-mark ``??'', for example, a simple echoing program might look like:
PROC echoing (CHAN BYTE kyb?, scr!, err!)
WHILE TRUE
BYTE b:
kyb ?? b
scr ! b
scr ! #FF
:
Indented under the extended input ``??'' are two processes. The first (during process) is
the one executed before the outputting process continues; the second (after process) is the one
executed after the outputting process has resumed. This second process may be missing,
in which case `SKIP' is assumed. The during process may not engage in the extended
event (`kyb' channel in the above example) since this would cause deadlock. The compiler checks
for this.
Tagged protocol channels are handled using a slightly different syntax (since we want during and
after processes for each variant). For example:
PROTOCOL TAGGED
CASE
empty
num; INT
:
PROC foo (CHAN TAGGED in?, CHAN BYTE out!)
WHILE TRUE
SEQ
in ?? CASE
empty
out ! '**'
INT n:
num; n
out ! BYTE (num /\ #FF)
out ! '**'
..
:
added 18/12/2001
As per the occam-3 specification, parameters and abbreviations may be declared
with the RESULT `prefix'. The primary purpose of these is to provide
more information to the compiler, so it can check that parameters are defined
when PROCs return, and that RESULT abbreviations are left
defined when they go out of scope.
Syntactically, the RESULT occurs where one might park a VAL,
for example:
PROC fac (VAL INT n, RESULT INT v)
SEQ
v :=
:
PROC thing ()
INT v:
SEQ
RESULT INT i IS v:
fac (6, i)
:
Two different implementations of RESULT abbreviations are supported. The first, and default,
treats the abbreviation as one without the RESULT keyword, but performs the additional check
(for definedness) when the variable leaves scope, e.g.:
RESULT TYPE a IS v:
P
becomes:
TYPE a IS v:
SEQ
P
The second implementation, selected with the `-zrv' compiler flag generates a fresh variable then
assigns into it. From the above:
TYPE anon:
SEQ
TYPE a IS anon:
P
v := anon
Although probably rare, there are cases where this second transformation will ultimately result in more
efficient code, especially if `v' is large and non-local (accessed through the static-link).
added 12/12/2001
Nested PROTOCOLs allow one PROTOCOL definition to be used within another. For example:
PROTOCOL simple IS INT:
PROTOCOL seq.proto IS INT; INT; simple:
PROTOCOL seq.other IS BYTE; seq.proto; INT::[]BYTE
PROTOCOL similar IS seq.other:
In the above, `seq.other' has the structure ``BYTE; INT; INT; INT; INT::[]BYTE''.
It should be noted that `similar' and `seq.other' are still distinct PROTOCOLs.
Tagged protocols can also be used, for example:
PROTOCOL tagged
CASE
empty
int; INT
data; BYTE; similar; INT16
:
PROTOCOL more.tags
CASE
FROM tagged
flibble; seq.proto
:
added 10/12/2001
The checking of SKIP guards in ALTs and PRI ALTs has been modified. For `ALT'
processes, the absence of a ``exp &'' pre-condition on a ``SKIP'' will result in a warning.
If the `-strict' option is used, non-pre-conditioned `SKIP's will generate errors (as was the
default behavior). This code, for example:
PROC wibble (CHAN INT in.0?, in.1?, out!)
INT v:
ALT
in.0 ? v
out ! v
SKIP
out ! 0
in.1 ? v
out ! v
:
will generate a warning about the non-pre-conditioned SKIP (or error with `-strict'). For
`PRI ALT's, a non-pre-conditioned SKIP guard is allowed, but only as the last (or only)
guard. The following is a valid example:
PROC wibble2 (CHAN INT in.0?, out!)
PRI ALT
INT v:
in.0 ? v
out ! v
SKIP
out ! (-1)
:
added 30/11/2001
On the theme of dynamic occam, recursion has now been implemented. This is still fairly basic and
there are several restrictions:
- Only PROCs can be recursive
- Only self-recursion is allowed
Recursive PROCs are declared by adding the ``RECURSIVE'' or ``REC'' keyword
to the procedure header. Here is an example:
REC PROC thing (VAL INT c, CHAN BYTE out!)
SEQ
out.string ("Hello world at ", 0, out!)
out.int (c, 0, out!)
out ! '*n'
IF
c = 0
SKIP
TRUE
thing (c - 1, out!)
:
This example is actually slightly silly, since we could do tail-call optimisation, thus removing the
recursion. This optimisation has not been implemented yet however.
added 14/11/2000
The ability to have STEP in replicators has been added to the compiler.
This allows the addition of ``STEP n'' to replicator expressions of (SEQ,
PAR, ALT and IF). For example:
SEQ i = 0 FOR 5 STEP 2
foo (i)
is equivalent to:
SEQ
foo (0)
foo (2)
foo (4)
foo (6)
foo (8)
The STEP expression is evaluated before the replication, and may be any
valid occam expression (including VALOF expressions) in SEQ, ALT and IF
replications. For PARallel replicators, the STEP expression must be
constant.
For efficiency, three loop-end (LEND) instructions are provided. One
is for a STEP of 1, another for a STEP of -1, and a final one for
arbitrary STEPs. For instance, having:
PRI ALT i = 4 FOR 5 STEP -1
is no more expensive than having:
PRI ALT i = 0 FOR 5
or
PRI ALT i = 0 FOR 5 STEP 1
added 23/10/2001
The direction of communication on channel parameters to PROCs can
now be specified in their formal and actual parameters. The compiler will
bear this in mind when checking the channel usage inside a PROC.
It also adds more information to a PROCs interface from the code
perspective. For example:
PROC foo (CHAN INT in?, out!, CHAN BOOL terminate?)
:
and:
CHAN INT c:
PAR
foo (c?, )
other (c!)
The usage of this also applies to channel abbreviations, for example:
PROC foo.2 ([8]CHAN INT64 in?, CHAN INT64 out!)
CHAN INT r? IS in[i]?:
:
the compiler will check that output channels are not abbreviated as input channels and visa-versa.
In strict mode (using the ``-s'' flag to kroc), usage of channel-direction specifiers
is enforced.
When referring to array slices, the direction specifier should go inside with the array, eg:
PROC thing ([]CHAN INT in?)
[]CHAN INT i? IS [in? FROM 2]:
:
added 23/10/2001
The usage of the ``OF'' keyword in channel specifiers is also optional now. For example:
PROC bar (CHAN INT in?, out!)
out ! 42
:
PROC switch ([2]CHAN INT in?, out!)
:
added 08/10/2001
Array constructors allow arrays to be created using a replicator and
an expression. An example of this is:
[10]INT array:
SEQ
array := [i = 0 FOR SIZE array | f(i)]
where `f()' is some expression, which might or might not involve `i'.
Internally, the array constructor is turned into a VALOF process,
so array-constructors may be used as parameters, or anywhere an
expression is valid. The (obvious) limitation here is that array-constructors
may not cause side-effects. The non-obvious limitation is that the count
must be a compiler-known constant. This will be fixed at some point in the near
future. The STEP addition
may also be used in array constructors. The general syntax for this
type of expression is:
"[" <repl> "=" <start> "FOR" <count> [ "STEP" <stride> ] "|" <expr> "]"
The line can be broken after the ``|'' to avoid things getting to long.
A more extreme example of array constructors might be:
PROC show.3d (VAL [][][]INT n, )
:
SEQ
show.3d ([x = 0 FOR 10 | [y = 0 FOR 10 | [z = 0 FOR 10 |
((x - z) * (y + z)) ]]])
INITIAL declarations now permit these to be used with dynamic MOBILE arrays. For example:
INITIAL MOBILE []INT x IS [i = 0 FOR 100 | foo(i)]:
added 01/10/2001
This new feature allows for PAR replicators to have a variable count
field. This uses the dynamic memory support provided by the run-time
system. A simple example of this is:
PROC foo (CHAN INT in?)
INT count:
SEQ
in ? count
PAR i = 0 FOR count
some.process (i)
:
In order to help support this, dynamic MOBILE []CHAN arrays are allowed,
but no checking is performed on them when used with a replicated PAR.
Run-time checking will probably be added at some point.
An example of this type of usage (to build a pipeline of processes) is:
PROC sort.pump (VAL INT size, CHAN INT in?, out!)
IF
size > 1
INITIAL MOBILE []CHAN INT pipe IS MOBILE [size - 1]CHAN INT:
PAR
sort.cell (in, pipe[0])
PAR i = 0 FOR (size - 2)
sort.cell (pipe[i], pipe[i+1])
sort.cell (pipe[size-2], out)
TRUE
sort.cell (in, out)
:
added 08/09/2001
Mobiles are a new type class in occam -- i.e. for most ordinary types, a compatible mobile version now exists.
Mobiles are different in one key way: assignment and communication of mobiles uses a movement semantics, instead of the usual copying
semantics. This implies that when a process outputs a mobile variable, it loses it -- and any subsequent attempt to read from the variable
is undefined (illegal).
To the programmer, mobiles offer a potential performance gain, with only minor intrusion to code. The run-time system (on a single-memory machine) implements
mobiles by pointer-swapping references, that is a very low-cost operation (mobile commstime is around 20ns more than the ordinary commstime). More importantly,
however, mobiles provide an efficient means to implement the familiar ``packet passing'' abstraction (in the past, occam programs have moved pointers
around at run-time, for efficiency, but this was wholly un-checked and required the use of in-line assembler).
Essentially, two basic types of mobile variables are supported:
- Static mobiles, whose size is known at compile-time
- Dynamic mobile arrays, whose size is only known at run-time
Static mobiles are allocated (by the compiler) into a global memory `heap' known as mobilespace. Like workspace and vectorspace, the required
size of this area is known at compile-time. Dynamic process creation (recursion, variable replicated PARs,
and forked processes) complicates this slightly. For mobilespace requiring dynamic processes, hooks
are allocated inside the enclosing mobilespace. Once dynamic memory has been allocated for mobilespace, it cannot be returned to the general memory pool --
since references inside mobilespace may have migrated into other processes. More details on the technical aspects of mobilespace can be found in the
`Mobile Data, Dynamic Allocation and Zero Aliasing: an occam Experiment' paper.
Dynamic mobile arrays are somewhat simpler, since they are largely run-time only -- but incur a greater overhead for allocation/release than static mobiles
(tens of nano-seconds).
Mobile variables can be declared in two ways: using the `MOBILE' keyword directly in the declaration; or declaring variables of a named `MOBILE' type.
The first, for example:
MOBILE INT x, y:
SEQ
x := 42
y := x
MOBILE []REAL64 data:
SEQ
data := MOBILE [128]REAL64
data[42] := 99.0
The first fragment declares two simple mobile integers. The second is slightly more interesting, requiring a special allocation assignment to create the
array. Until they are allocated, dynamic mobile arrays have zero-size and are considered to be undefined (as far as the undefinedness checker is concerned). Any
attempt to access a non-existant element will result in the usual run-time (range) error.
Communication behaves in much the same way as assignment, for example:
CHAN MOBILE []INT vals:
PAR
MOBILE []INT x:
SEQ
x := MOBILE [10]INT
SEQ i = 0 FOR SIZE x
x[i] := i
c ! x
MOBILE []INT v:
INT x:
SEQ
c ? v
x := v[5]
As far as type compatability goes: wherever a non-mobile variable is valid, its mobile equivalent is valid too. For example:
MOBILE REAL64 m.v:
REAL64 s.v:
SEQ
m.v := 24.5 (REAL64)
s.v := m.v
Assignment is only considered mobile if both variables (LHS and RHS) are themselves mobile -- i.e. mixed mobile/non-mobile assignment
results in the usual copy semantics. Communication is only mobile if the channel protocol is mobile. The following, for example,
uses non-mobile communication:
CHAN [5]INT c:
PAR
MOBILE [5]INT x:
SEQ
SEQ i = 0 FOR SIZE x
x[i] := i
c ! x
MOBILE [5]INT v:
INT x:
SEQ
c ? v
x := v[5]
Changing the channel declaration to `CHAN MOBILE [5]INT c:' would, as expected, result in mobile communication. This is static
mobile communication, however, since the size of `[5]INT' is known at compile-time.
When a non-mobile variable is used in mobile communication or assignment, the compiler automatically promotes the variable to its
mobile equivalent. For dynamic mobile arrays, this includes automatic allocation of the array, making the programmer's life somewhat easier.
The second method of declaring mobiles involves declaring mobile types. For example:
DATA TYPE STRING IS MOBILE []BYTE:
DATA TYPE PACKET
MOBILE RECORD
[1024]BYTE data:
INT offset:
:
Then using that type as usual. For example, and demonstrating automatic mobile promotion:
STRING s:
PACKET p:
SEQ
s := "hello, occam world!*n"
[p[data] FOR SIZE s] := s
p[offset] := SIZE s
The array constructor may also be used with dynamic mobile arrays and automatic promotion. For example:
CHAN MOBILE []REAL64 c:
PAR
c ! [i = 0 FOR 128 | ((REAL64 i) * PI) / 128.0]
MOBILE []REAL64 data:
SEQ
c ? data
In some cases, it may be desirable to prevent mobile assignment or communication, where movement semantics are the default.
This is done through use of the `CLONE' operator, that produces a duplicate of its mobile argument. The
compiler will not automatically promote a non-mobile operand given to CLONE, thus `CLONE 5' is illegal.
The CLONE operator also helps resolve potential ambiguities. The following fragment, for example, leaves `x' and `y' both
defined:
MOBILE INT x, y:
SEQ
x := 42
y := (x + 5)
But if we later removed the `+ 5':
MOBILE INT x, y:
SEQ
x := 42
y := (x)
It is not immediately clear whether this results in movement or copying semantics -- i.e. whether `x' is defined after the second assignment.
In fact, the compiler will use copying semantics for this -- two reasons: firstly, because of the brackets; and secondly, because any mobile type less than
or equal to 8 bytes is automatically made non-mobile (because the equivalent non-mobile assignments and communications are quicker).
The CLONE operator can clear this ambiguity up, however:
MOBILE INT x, y:
SEQ
x := 42
y := CLONE x
A more practical example might be:
DATA TYPE STRING IS MOBILE []BYTE:
CHAN STRING c:
PAR
STRING x:
SEQ
x := "hello, world!*n"
c ! CLONE x
[x FROM 7 FOR 5] := "occam"
c ! x
STRING v, w:
SEQ
c ? v
c ? w
Or in a mobile `tap' process (using the extended rendezvous), for example:
PROC mi.tap (CHAN MOBILE []INT in?, out!, report!)
WHILE TRUE
MOBILE []INT x:
in ?? x
out ! CLONE x
report ! x
:
The compiler (and run-time system) also support multi-dimensional dynamic mobile arrays. These can be used
with automatic-promotion and array constructors too. For example:
DATA TYPE I.MATRIX IS MOBILE [][]INT:
PAR
I.MATRIX x:
SEQ
x := [i = 0 FOR 64 | [j = 0 FOR 64 | (i * j)]]
c ! x
I.MATRIX m:
INT sum:
SEQ
c ? m
sum := 0
SEQ i = 0 FOR SIZE m
SEQ j = 0 FOR SIZE m[i]
sum := sum + m[i][j]
|